From 72b135d0252bb51c929147b27d2cc138b78eae4d Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 4 Sep 2013 12:44:05 +0200 Subject: [PATCH] Bug 12403: Add a batch record deletion This patch offers a new tool for deleting records. Biblios and authorities will can to be deleted with a simple list of biblionumber or authid. This feature adds: - a new pl/tt files tools/batch_delete_records - a new permission: tools > records_batchdel Test plan for biblios: 1/ 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 > "Delete" - generating a list from a report 2/ On the "Batch record deletion" tool verify: - biblios with issues cannot be deleted (checkbox disabled and line in red). - information is correct. - sort functions work on each columns. - the items, reserves and issues values are correct. 3/ After clicking on the "Delete selected recors" button, verify: - reserves, items and biblio have successful been deleted. - if an error occurs, the tool display an error message. Test plan for authority: 1/ Generate a list of authid using a report: 2/ On the "Batch record deletion" tool verify: - authorities are display with the summary. - the count usage (used in X biblios) is correct. 3/ After clicking on the "Delete selected recors" button, verify: - The authorities have successful been deleted. - if an error occurs, the tool display an error message. Signed-off-by: Brendan Gallagher Signed-off-by: Katrin Fischer Signed-off-by: Tomas Cohen Arazi --- .../prog/en/includes/tools-menu.inc | 3 + .../prog/en/modules/basket/basket.tt | 6 + .../en/modules/tools/batch_delete_records.tt | 236 ++++++++++++++++++ .../prog/en/modules/tools/tools-home.tt | 7 +- tools/batch_delete_records.pl | 231 +++++++++++++++++ 5 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt create mode 100755 tools/batch_delete_records.pl diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc index 963195deae..ea3cf820b6 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -56,6 +56,9 @@ [% IF ( CAN_user_tools_items_batchmod ) %]
  • Batch item modification
  • [% END %] + [% IF CAN_user_tools_records_batchdel %] +
  • Batch record deletion
  • + [% END %] [% IF ( CAN_user_tools_export_catalog ) %]
  • Export data
  • [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt index 5a3d22ef3e..60fd2e4206 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/basket/basket.tt @@ -101,6 +101,12 @@ function placeHold () { [% END %] +
    + Actions + +
    Print Empty and close Hide window diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt new file mode 100644 index 0000000000..f7fa79e5d4 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt @@ -0,0 +1,236 @@ +[% PROCESS 'authorities-search-results.inc' %] +[% INCLUDE 'doc-head-open.inc' %] +Koha › Tools › Batch record deletion +[% INCLUDE 'doc-head-close.inc' %] + +[% INCLUDE 'datatables.inc' %] + + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
    +
    +
    +
    +

    Batch record deletion

    + [% FOREACH message IN messages %] + [% IF message.type == 'success' %] +
    + [% ELSIF message.type == 'warning' %] +
    + [% ELSIF message.type == 'error' %] +
    + [% 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 %] +
    + [% END %] + [% IF op == 'form' %] +
    +
    + Record type +
      +
    1. +
    2. +
    +
    +
    + Use a file +
      +
    1. +
    +
    +
    + Or enter a list of record numbers +
      +
    1. + + +
    2. +
    +
    +
    + + + Cancel +
    +
    + [% ELSIF op == 'list' %] + [% IF records %] + [% IF recordtype == 'biblio' %] + +
    + + + + + + + + + + + + + [% FOR biblio IN records %] + + + + + + + + + [% END %] + +
    BiblionumberTitleItemsReservesIssues
    [% biblio.biblionumber %][% biblio.title %][% biblio.itemnumbers.size %][% biblio.reserves.size %][% biblio.issues_count %]
    +
    Reminder: this action will delete all selected biblios, attached subscriptions, existing holds and items!
    + [% ELSE %] + + + + + + + + + + + + + [% FOR authority IN records %] + + + + + + + [% END %] + +
    AuthidSummaryUsed in
    [% authority.authid %][% PROCESS authresult summary=authority.summary %][% authority.count_usage %] biblio(s)
    +
    Reminder: this action will delete all selected authorities!
    + [% END %] +
    + + + + Cancel +
    +
    + [% 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 %] +

    New batch record deletion

    + [% ELSE %] + No action defined for the template. + [% END %] +
    +
    +
    + [% INCLUDE 'tools-menu.inc' %] +
    +
    +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt index 723dcbac57..82dffca1b9 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -121,7 +121,12 @@
    Batch item modification
    Modify items in a batch
    [% END %] - + + [% IF CAN_user_tools_records_batchdel %] +
    Batch record deletion
    +
    Delete a batch of records (biblios or authorities)
    + [% END %] + [% IF ( CAN_user_tools_export_catalog ) %]
    Export data
    Export bibliographic, holdings, and authority records
    diff --git a/tools/batch_delete_records.pl b/tools/batch_delete_records.pl new file mode 100755 index 0000000000..7bb8489722 --- /dev/null +++ b/tools/batch_delete_records.pl @@ -0,0 +1,231 @@ +#!/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 +# + +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; -- 2.20.1