--- /dev/null
+[% PROCESS 'authorities-search-results.inc' %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha › Tools › Batch record deletion</title>
+[% INCLUDE 'doc-head-close.inc' %]
+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'datatables.inc' %]
+<script type="text/javascript" src="[% interface %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script>
+<script type="text/javascript">
+//<![CDATA[
+var MSG_CANNOT_BE_DELETED = _("This record cannot be deleted, at least one item is currently issued");
+$(document).ready(function() {
+ $("#selectall").click(function(e){
+ e.preventDefault();
+ $(".records").checkCheckboxes();
+ });
+ $("#clearall").click(function(e){
+ e.preventDefault();
+ $(".records").unCheckCheckboxes();
+ });
+ $("#selectwithoutitems").click(function(e){
+ e.preventDefault();
+ $("#biblios").checkCheckboxes(":input[data-items='0']:not(:disabled)");
+ });
+ $("#selectnotreserved").click(function(e){
+ e.preventDefault();
+ $("#biblios").checkCheckboxes(":input[data-reserves='0']:not(:disabled)");
+
+ });
+ $("#clearlinkedtobiblio").click(function(e){
+ e.preventDefault();
+ $("#authorities").unCheckCheckboxes(":not(input[data-usage='0'])");
+ });
+ $("#selectall").click();
+
+ [% IF recordtype == 'biblio' %]
+ $(".records input:checkbox[data-issues!='0']").each(function(){
+ $(this).attr('title', MSG_CANNOT_BE_DELETED)
+ $(this).attr('disabled', true);
+ $(this).attr('checked', false);
+ $(this).parents('tr').find('td').css('background-color', 'red');
+ });
+ [% END %]
+
+ $("table#biblios").dataTable($.extend(true, {}, dataTablesDefaults, {
+ "aoColumnDefs": [
+ { "aTargets": [ 0 ], "bSortable": false, "bSearchable": false },
+ { "aTargets": [ 3, 4 ], "sType": "num-html" }
+ ],
+ "sDom": 't',
+ "aaSorting": [],
+ "bPaginate": false
+ }));
+
+ $("table#authorities").dataTable($.extend(true, {}, dataTablesDefaults, {
+ "aoColumnDefs": [
+ { "aTargets": [ 0 ], "bSortable": false, "bSearchable": false },
+ { "aTargets": [ 3 ], "sType": "num-html" }
+ ],
+ "sDom": 't',
+ "aaSorting": [],
+ "bPaginate": false
+ }));
+});
+//]]>
+</script>
+</head>
+<body id="tools_batch_delete_records" 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_delete_records.pl">Batch record deletion</a>
+</div>
+
+<div id="doc3" class="yui-t2">
+<div id="bd">
+<div id="yui-main">
+<div class="yui-b">
+ <h1>Batch record deletion</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 %]
+ [% IF 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 == 'item_issued' %]
+ At least one item issued for the biblio [% message.biblionumber %].
+ [% ELSIF message.code == 'reserve_not_cancelled' %]
+ The biblio [% message.biblionumber %] has not been deleted. A reserve (reserve_id [% message.reserve_id %]) caused an error on cancel.
+ [% ELSIF message.code == 'item_not_deleted' %]
+ The biblio [% message.biblionumber %] has not been deleted. An item (itemnumber [% message.itemnumber %]) caused an error on delete.
+ [% ELSIF message.code == 'biblio_not_deleted' %]
+ The biblio [% message.biblionumber %] has not been deleted. An error occurred on deleting it.
+ [% ELSIF message.code == 'authority_not_deleted' %]
+ The authority [% message.authid %] has not been deleted. An error occurred on deleting it.
+ [% ELSIF message.code == 'biblio_deleted' %]
+ The biblio [% message.biblionumber %] has successfully been deleted.
+ [% ELSIF message.code == 'authority_deleted' %]
+ The authority [% message.authid %] has successfully been deleted.
+ [% END %]
+ [% IF message.error %]
+ (The error was: [% message.error%], see the Koha logfile for more information).
+ [% END %]
+ </div>
+ [% END %]
+ [% IF op == 'form' %]
+ <form method="post" enctype="multipart/form-data" action="/cgi-bin/koha/tools/batch_delete_records.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="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 op == 'list' %]
+ [% IF records %]
+ [% IF recordtype == 'biblio' %]
+ <div id="toolbar">
+ <a id="selectall" href="#">Select All</a>
+ | <a id="clearall" href="#">Clear All</a>
+ | <a id="selectwithoutitems" href="#">Select without items</a>
+ | <a id="selectnotreserved" href="#">Select not reserved</a>
+ </div>
+ <form action="/cgi-bin/koha/tools/batch_delete_records.pl" method="post">
+ <table id="biblios" class="records">
+ <thead>
+ <tr>
+ <th></th>
+ <th>Biblionumber</th>
+ <th>Title</th>
+ <th>Items</th>
+ <th>Reserves</th>
+ <th>Issues</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/catalogue/moredetail.pl?biblionumber=[% biblio.biblionumber %]">[% biblio.itemnumbers.size %]</a></td>
+ <td><a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio.biblionumber %]">[% biblio.reserves.size %]</a></td>
+ <td><a href="/cgi-bin/koha/catalogue/issuehistory.pl?biblionumber=[% biblio.biblionumber %]">[% biblio.issues_count %]</a></td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ <div class="note">Reminder: this action will delete all selected biblios, attached subscriptions, existing holds and items!</div>
+ [% ELSE %]
+ <div id="toolbar">
+ <a id="selectall" href="#">Select All</a>
+ | <a id="clearall" href="#">Clear All</a>
+ | <a id="clearlinkedtobiblio" href="#">Clear linked to biblio </a>
+ </div>
+ <form action="/cgi-bin/koha/tools/batch_delete_records.pl" method="post">
+ <table id="authorities" class="records">
+ <thead>
+ <tr>
+ <th></th>
+ <th>Authid</th>
+ <th>Summary</th>
+ <th>Used in</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/catalogue/search.pl?type=intranet&op=do_search&idx=an,phr&q=[% authority.authid %]">[% authority.count_usage %] biblio(s)</a></td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ <div class="note">Reminder: this action will delete all selected authorities!</div>
+ [% END %]
+ <fieldset class="action">
+ <input type="hidden" name="op" value="delete" />
+ <input type="hidden" name="recordtype" value="[% recordtype %]" />
+ <input type="submit" value="Delete selected records" class="button" />
+ <a class="cancel" href="/cgi-bin/koha/tools/batch_delete_records.pl">Cancel</a>
+ </fieldset>
+ </form>
+ [% ELSE %]
+ There is no record ids defined.
+ [% END %]
+ [% ELSIF op == 'report' %]
+ [% IF report.total_records == report.total_success %]
+ All records have successfully been deleted!
+ [% ELSIF report.total_success == 0 %]
+ No record has been deleted, some errors occurred.
+ [% ELSE %]
+ [% report.total_success %] / [% report.total_records %] records have successfully been deleted but some errors occurred.
+ [% END %]
+ <p><a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
+ [% ELSE %]
+ No action defined for the template.
+ [% END %]
+</div>
+</div>
+<div class="yui-b">
+ [% INCLUDE 'tools-menu.inc' %]
+</div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
--- /dev/null
+#!/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;
+use C4::Output;
+use C4::AuthoritiesMarc;
+use C4::Biblio;
+
+my $input = new CGI;
+my $dbh = C4::Context->dbh;
+my $op = $input->param('op') // q|form|;
+my $recordtype = $input->param('recordtype') // 'biblio';
+
+my ($template, $loggedinuser, $cookie) = get_template_and_user({
+ template_name => 'tools/batch_delete_records.tt',
+ query => $input,
+ type => "intranet",
+ authnotrequired => 0,
+ flagsrequired => { tools => 'biblio_batchdel' },
+});
+
+my @records;
+my @messages;
+if ( $op eq 'form' ) {
+ # Display the form
+ $template->param( op => 'form' );
+} elsif ( $op eq 'list' ) {
+ # List all records to process
+ my @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;
+ }
+ $biblio->{itemnumbers} = C4::Items::GetItemnumbersForBiblio( $record_id );
+ $biblio->{reserves} = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $record_id });
+ $biblio->{issues_count} = C4::Biblio::CountItemsIssued( $record_id );
+ push @records, $biblio;
+ } else {
+ # Retrieve authority information
+ my $authority = C4::AuthoritiesMarc::GetAuthority( $record_id );
+ unless ( $authority ) {
+ push @messages, {
+ type => 'warning',
+ code => 'authority_not_exists',
+ authid => $record_id,
+ };
+ next;
+ }
+
+ $authority = {
+ authid => $record_id,
+ summary => C4::AuthoritiesMarc::BuildSummary( $authority, $record_id ),
+ count_usage => C4::AuthoritiesMarc::CountUsage( $record_id ),
+ };
+ push @records, $authority;
+ }
+ }
+ $template->param(
+ records => \@records,
+ op => 'list',
+ );
+} elsif ( $op eq 'delete' ) {
+ # We want to delete selected records!
+ my @record_ids = $input->param('record_id');
+ my $dbh = C4::Context->dbh;
+ $dbh->{AutoCommit} = 0;
+ $dbh->{RaiseError} = 1;
+
+ my $error;
+ my $report = {
+ total_records => 0,
+ total_success => 0,
+ };
+ 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;
+ # First, checking if issues exist.
+ # If yes, nothing to do
+ if ( C4::Biblio::CountItemsIssued( $biblionumber ) ) {
+ push @messages, {
+ type => 'warning',
+ code => 'item_issued',
+ biblionumber => $biblionumber,
+ };
+ $dbh->rollback;
+ next;
+ }
+
+ # Cancel reserves
+ my $reserves = C4::Reserves::GetReservesFromBiblionumber({ biblionumber => $biblionumber });
+ for my $reserve ( @$reserves ) {
+ eval{
+ C4::Reserves::CancelReserve( { reserve_id => $reserve->{reserve_id} } );
+ };
+ if ( $@ ) {
+ push @messages, {
+ type => 'error',
+ code => 'reserve_not_cancelled',
+ biblionumber => $biblionumber,
+ reserve_id => $reserve->{reserve_id},
+ error => $@,
+ };
+ $dbh->rollback;
+ next RECORD_IDS;
+ }
+ }
+
+ # Delete items
+ my @itemnumbers = @{ C4::Items::GetItemnumbersForBiblio( $biblionumber ) };
+ ITEMNUMBER: for my $itemnumber ( @itemnumbers ) {
+ my $error = eval { C4::Items::DelItemCheck( $dbh, $biblionumber, $itemnumber ) };
+ if ( $error != 1 or $@ ) {
+ push @messages, {
+ type => 'error',
+ code => 'item_not_deleted',
+ biblionumber => $biblionumber,
+ itemnumber => $itemnumber,
+ error => ($@ ? $@ : $error),
+ };
+ $dbh->rollback;
+ next BIBLIONUMBER;
+ }
+ }
+
+ # Finally, delete the biblio
+ my $error = eval {
+ C4::Biblio::DelBiblio( $biblionumber );
+ };
+ if ( $error or $@ ) {
+ push @messages, {
+ type => 'error',
+ code => 'biblio_not_deleted',
+ biblionumber => $biblionumber,
+ error => ($@ ? $@ : $error),
+ };
+ $dbh->rollback;
+ next;
+ }
+
+ push @messages, {
+ type => 'success',
+ code => 'biblio_deleted',
+ biblionumber => $biblionumber,
+ };
+ $report->{total_success}++;
+ $dbh->commit;
+ } else {
+ # Authorities
+ my $authid = $record_id;
+ my $r = eval { C4::AuthoritiesMarc::DelAuthority( $authid ) };
+ if ( $r eq '0E0' or $@ ) {
+ push @messages, {
+ type => 'error',
+ code => 'authority_not_deleted',
+ authid => $authid,
+ error => ($@ ? $@ : 0),
+ };
+ $dbh->rollback;
+ next;
+ } else {
+ push @messages, {
+ type => 'success',
+ code => 'authority_deleted',
+ authid => $authid,
+ };
+ $report->{total_success}++;
+ $dbh->commit;
+ }
+ }
+ }
+ $template->param(
+ op => 'report',
+ report => $report,
+ );
+}
+
+$template->param(
+ messages => \@messages,
+ recordtype => $recordtype,
+);
+
+output_html_with_http_headers $input, $cookie, $template->output;