From 4f5777da3a8dd71fc92517469cf4d26da8f1a2fd Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Tue, 28 May 2013 16:24:53 +0200 Subject: [PATCH] Bug 8064: Merge several biblios This patch improves the existing merging tool by adding possibility to merge more than 2 biblios. There is no functional changes: - Add some biblios to a list - In the list check some biblios and click on 'Merge selected records' - Choose the biblio which will be kept, all others will be deleted - On the next page you have all biblios you chose in tabs (left side of the screen) and the preview of result (right side) - Pick some fields or subfields from records that will be deleted or delete some fields from reference record. - Click on 'Merge', if there is no errors you are redirected to the biblio view. Added checks for non-repeatable subfields Added checks for mandatory fields and subfields before submitting the form. Added a final report which display deleted records (see syspref MergeReportFields) --- cataloguing/merge.pl | 359 +++++----- installer/data/mysql/sysprefs.sql | 1 + installer/data/mysql/updatedatabase.pl | 14 + .../en/modules/admin/preferences/cataloguing.pref | 9 + .../prog/en/modules/cataloguing/merge.tt | 724 +++++++++++--------- .../prog/en/modules/virtualshelves/shelves.tt | 20 +- 6 files changed, 648 insertions(+), 479 deletions(-) diff --git a/cataloguing/merge.pl b/cataloguing/merge.pl index 019b468..8f41e48 100755 --- a/cataloguing/merge.pl +++ b/cataloguing/merge.pl @@ -1,6 +1,5 @@ #!/usr/bin/perl - # Copyright 2009 BibLibre # Parts Copyright Catalyst IT 2011 # @@ -19,8 +18,8 @@ # with Koha; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -use strict; -#use warnings; FIXME - Bug 2505 +use Modern::Perl; + use CGI; use C4::Output; use C4::Auth; @@ -32,14 +31,14 @@ use C4::Reserves qw/MergeHolds/; use C4::Acquisition qw/ModOrder GetOrdersByBiblionumber/; my $input = new CGI; -my @biblionumber = $input->param('biblionumber'); +my @biblionumbers = $input->param('biblionumber'); my $merge = $input->param('merge'); my @errors; my ( $template, $loggedinuser, $cookie ) = get_template_and_user( { - template_name => "cataloguing/merge.tmpl", + template_name => "cataloguing/merge.tt", query => $input, type => "intranet", authnotrequired => 0, @@ -53,166 +52,208 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( if ($merge) { my $dbh = C4::Context->dbh; - my $sth; # Creating a new record from the html code - my $record = TransformHtmlToMarc( $input ); - my $tobiblio = $input->param('biblio1'); - my $frombiblio = $input->param('biblio2'); + my $record = TransformHtmlToMarc( $input ); + my $ref_biblionumber = $input->param('ref_biblionumber'); + @biblionumbers = grep { $_ != $ref_biblionumber } @biblionumbers; + + # prepare report + my @report_records; + my $report_fields_str = $input->param('report_fields'); + $report_fields_str ||= C4::Context->preference('MergeReportFields'); + my @report_fields; + foreach my $field_str (split /,/, $report_fields_str) { + if ($field_str =~ /(\d{3})([0-9a-z]*)/) { + my ($field, $subfields) = ($1, $2); + push @report_fields, { + tag => $field, + subfields => [ split //, $subfields ] + } + } + } # Rewriting the leader - $record->leader(GetMarcBiblio($tobiblio)->leader()); + $record->leader(GetMarcBiblio($ref_biblionumber)->leader()); my $frameworkcode = $input->param('frameworkcode'); my @notmoveditems; # Modifying the reference record - ModBiblio($record, $tobiblio, $frameworkcode); + ModBiblio($record, $ref_biblionumber, $frameworkcode); # Moving items from the other record to the reference record - # Also moving orders from the other record to the reference record, only if the order is linked to an item of the other record - my $itemnumbers = get_itemnumbers_of($frombiblio); - foreach my $itloop ($itemnumbers->{$frombiblio}) { - foreach my $itemnumber (@$itloop) { - my $res = MoveItemFromBiblio($itemnumber, $frombiblio, $tobiblio); - if (not defined $res) { - push @notmoveditems, $itemnumber; + foreach my $biblionumber (@biblionumbers) { + my $itemnumbers = get_itemnumbers_of($biblionumber); + foreach my $itemnumber (@{ $itemnumbers->{$biblionumber} }) { + my $res = MoveItemFromBiblio($itemnumber, $biblionumber, $ref_biblionumber); + if (not defined $res) { + push @notmoveditems, $itemnumber; + } } } - } # If some items could not be moved : if (scalar(@notmoveditems) > 0) { my $itemlist = join(' ',@notmoveditems); push @errors, { code => "CANNOT_MOVE", value => $itemlist }; } - # Moving subscriptions from the other record to the reference record - my $subcount = CountSubscriptionFromBiblionumber($frombiblio); - if ($subcount > 0) { - $sth = $dbh->prepare("UPDATE subscription SET biblionumber = ? WHERE biblionumber = ?"); - $sth->execute($tobiblio, $frombiblio); - - $sth = $dbh->prepare("UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ?"); - $sth->execute($tobiblio, $frombiblio); - + my $sth_subscription = $dbh->prepare(" + UPDATE subscription SET biblionumber = ? WHERE biblionumber = ? + "); + my $sth_subscriptionhistory = $dbh->prepare(" + UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ? + "); + my $sth_serial = $dbh->prepare(" + UPDATE serial SET biblionumber = ? WHERE biblionumber = ? + "); + + my $report_header = {}; + foreach my $biblionumber ($ref_biblionumber, @biblionumbers) { + # build report + my $marcrecord = GetMarcBiblio($biblionumber); + my %report_record = ( + biblionumber => $biblionumber, + fields => {}, + ); + foreach my $field (@report_fields) { + my @marcfields = $marcrecord->field($field->{tag}); + foreach my $marcfield (@marcfields) { + my $tag = $marcfield->tag(); + my %subfields; + if (scalar @{$field->{subfields}}) { + foreach my $subfield (@{$field->{subfields}}) { + my @values = $marcfield->subfield($subfield); + $report_header->{ $tag . $subfield } = 1; + push @{ $report_record{fields}->{$tag . $subfield} }, @values; + } + } elsif ($field->{tag} gt '009') { + my @marcsubfields = $marcfield->subfields(); + foreach my $marcsubfield (@marcsubfields) { + my ($code, $value) = @$marcsubfield; + $report_header->{ $tag . $code } = 1; + push @{ $report_record{fields}->{ $tag . $code } }, $value; + } + } else { + $report_header->{ $tag . '@' } = 1; + push @{ $report_record{fields}->{ $tag .'@' } }, $marcfield->data(); + } + } + } + push @report_records, \%report_record; } - # Moving serials - $sth = $dbh->prepare("UPDATE serial SET biblionumber = ? WHERE biblionumber = ?"); - $sth->execute($tobiblio, $frombiblio); - - # TODO : Moving reserves + foreach my $biblionumber (@biblionumbers) { + # Moving subscriptions from the other record to the reference record + my $subcount = CountSubscriptionFromBiblionumber($biblionumber); + if ($subcount > 0) { + $sth_subscription->execute($ref_biblionumber, $biblionumber); + $sth_subscriptionhistory->execute($ref_biblionumber, $biblionumber); + } - # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) - my @allorders = GetOrdersByBiblionumber($frombiblio); - my @tobiblioitem = GetBiblioItemByBiblioNumber ($tobiblio); - my $tobiblioitem_biblioitemnumber = $tobiblioitem [0]-> {biblioitemnumber }; - foreach my $myorder (@allorders) { - $myorder->{'biblionumber'} = $tobiblio; - ModOrder ($myorder); - # TODO : add error control (in ModOrder?) - } + # Moving serials + $sth_serial->execute($ref_biblionumber, $biblionumber); + + # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) + my @allorders = GetOrdersByBiblionumber($frombiblio); + my @tobiblioitem = GetBiblioItemByBiblioNumber ($tobiblio); + my $tobiblioitem_biblioitemnumber = $tobiblioitem [0]-> {biblioitemnumber }; + foreach my $myorder (@allorders) { + $myorder->{'biblionumber'} = $tobiblio; + ModOrder ($myorder); + # TODO : add error control (in ModOrder?) + } - # Deleting the other record - if (scalar(@errors) == 0) { - # Move holds - MergeHolds($dbh,$tobiblio,$frombiblio); - my $error = DelBiblio($frombiblio); - push @errors, $error if ($error); + # Deleting the other records + if (scalar(@errors) == 0) { + # Move holds + MergeHolds($dbh, $ref_biblionumber, $biblionumber); + my $error = DelBiblio($biblionumber); + push @errors, $error if ($error); + } } # Parameters $template->param( - result => 1, - biblio1 => $input->param('biblio1') + result => 1, + report_records => \@report_records, + report_header => $report_header, + ref_biblionumber => $input->param('ref_biblionumber') ); #------------------------- # Show records to merge #------------------------- } else { - my $mergereference = $input->param('mergereference'); - my $biblionumber = $input->param('biblionumber'); - - if (scalar(@biblionumber) != 2) { - push @errors, { code => "WRONG_COUNT", value => scalar(@biblionumber) }; - } - else { - my $data1 = GetBiblioData($biblionumber[0]); - my $record1 = GetMarcBiblio($biblionumber[0]); - - my $data2 = GetBiblioData($biblionumber[1]); - my $record2 = GetMarcBiblio($biblionumber[1]); - - # Checks if both records use the same framework - my $frameworkcode1 = &GetFrameworkCode($biblionumber[0]); - my $frameworkcode2 = &GetFrameworkCode($biblionumber[1]); - - - my $subtitle1 = GetRecordValue('subtitle', $record1, $frameworkcode1); - my $subtitle2 = GetRecordValue('subtitle', $record2, $frameworkcode1); - - if ($mergereference) { - - my $framework; - if ($frameworkcode1 ne $frameworkcode2) { - $framework = $input->param('frameworkcode') - or push @errors, "Famework not selected."; + my $ref_biblionumber = $input->param('ref_biblionumber'); + + if ($ref_biblionumber) { + my $framework = $input->param('frameworkcode'); + $framework //= GetFrameworkCode($ref_biblionumber); + + # Getting MARC Structure + my $tagslib = GetMarcStructure(1, $framework); + + # Creating a loop for display + my @records; + foreach my $biblionumber (@biblionumbers) { + my $marcrecord = GetMarcBiblio($biblionumber); + my $frameworkcode = GetFrameworkCode($biblionumber); + my $record = { + biblionumber => $biblionumber, + record => $marcrecord, + frameworkcode => $frameworkcode, + display => _createMarcHash($marcrecord, $tagslib), + }; + if ($ref_biblionumber and $ref_biblionumber == $biblionumber) { + $record->{reference} = 1; + $template->param(ref_record => $record); + unshift @records, $record; } else { - $framework = $frameworkcode1; + push @records, $record; } + } - # Getting MARC Structure - my $tagslib = GetMarcStructure(1, $framework); - - my $notreference = ($biblionumber[0] == $mergereference) ? $biblionumber[1] : $biblionumber[0]; - - # Creating a loop for display - my @record1 = _createMarcHash(GetMarcBiblio($mergereference), $tagslib); - my @record2 = _createMarcHash(GetMarcBiblio($notreference), $tagslib); + my ($biblionumbertag) = GetMarcFromKohaField('biblio.biblionumber'); - # Parameters - $template->param( - biblio1 => $mergereference, - biblio2 => $notreference, - mergereference => $mergereference, - record1 => @record1, - record2 => @record2, - framework => $framework, - ); + # Parameters + $template->param( + ref_biblionumber => $ref_biblionumber, + records => \@records, + framework => $framework, + biblionumbertag => $biblionumbertag, + MergeReportFields => C4::Context->preference('MergeReportFields'), + ); + } + else { + my @records; + foreach my $biblionumber (@biblionumbers) { + my $frameworkcode = GetFrameworkCode($biblionumber); + my $record = { + biblionumber => $biblionumber, + data => GetBiblioData($biblionumber), + frameworkcode => $frameworkcode, + }; + push @records, $record; } - else { - # Ask the user to choose which record will be the kept - $template->param( - choosereference => 1, - biblio1 => $biblionumber[0], - biblio2 => $biblionumber[1], - title1 => $data1->{'title'}, - subtitle1 => $subtitle1, - title2 => $data2->{'title'}, - subtitle2 => $subtitle2 + $template->param( + choosereference => 1, + records => \@records, + ); + + my $frameworks = getframeworks; + my @frameworkselect; + foreach my $thisframeworkcode ( keys %$frameworks ) { + my %row = ( + value => $thisframeworkcode, + frameworktext => $frameworks->{$thisframeworkcode}->{'frameworktext'}, ); - if ($frameworkcode1 ne $frameworkcode2) { - my $frameworks = getframeworks; - my @frameworkselect; - foreach my $thisframeworkcode ( keys %$frameworks ) { - my %row = ( - value => $thisframeworkcode, - frameworktext => $frameworks->{$thisframeworkcode}->{'frameworktext'}, - ); - if ($frameworkcode1 eq $thisframeworkcode){ - $row{'selected'} = 1; - } - push @frameworkselect, \%row; - } - $template->param( - frameworkselect => \@frameworkselect, - frameworkcode1 => $frameworkcode1, - frameworkcode2 => $frameworkcode2, - ); - } + push @frameworkselect, \%row; } + $template->param( + frameworkselect => \@frameworkselect, + ); } } @@ -232,57 +273,51 @@ exit; # Functions # ------------------------ sub _createMarcHash { - my $record = shift; + my $record = shift; my $tagslib = shift; + my $hash = {}; my @array; my @fields = $record->fields(); - foreach my $field (@fields) { - my $fieldtag = $field->tag(); - if ($fieldtag < 10) { - if ($tagslib->{$fieldtag}->{'@'}->{'tab'} >= 0) { - push @array, { - field => [ - { + my $fieldtag = $field->tag(); + if ($fieldtag < 10) { + if ($tagslib->{$fieldtag}->{'@'}->{'tab'} >= 0) { + push @{ $hash->{fields} }, { tag => $fieldtag, key => createKey(), value => $field->data(), - } - ] }; - } - } else { - my @subfields = $field->subfields(); - my @subfield_array; - foreach my $subfield (@subfields) { - if ($tagslib->{$fieldtag}->{@$subfield[0]}->{'tab'} >= 0) { - push @subfield_array, { - subtag => @$subfield[0], - subkey => createKey(), - value => @$subfield[1], - }; - } - - } + } + } else { + if ($fieldtag ne '995' and defined $tagslib->{$fieldtag}) { + my @subfields = $field->subfields(); + my @subfield_array; + foreach my $subfield (@subfields) { + if (defined $tagslib->{$fieldtag}->{@$subfield[0]}->{tab} + and $tagslib->{$fieldtag}->{@$subfield[0]} ne '' + and $tagslib->{$fieldtag}->{@$subfield[0]}->{tab} >= 0) { + push @subfield_array, { + subtag => @$subfield[0], + subkey => createKey(), + value => @$subfield[1], + }; + } + } - if ($tagslib->{$fieldtag}->{'tab'} >= 0 && $fieldtag ne '995') { - push @array, { - field => [ - { - tag => $fieldtag, - key => createKey(), - indicator1 => $field->indicator(1), - indicator2 => $field->indicator(2), - subfield => [@subfield_array], + if (defined $tagslib->{$fieldtag} and $fieldtag ne '995') { + push @{ $hash->{fields} }, { + tag => $fieldtag, + key => createKey(), + indicator1 => $field->indicator(1), + indicator2 => $field->indicator(2), + subfield => [@subfield_array], + }; } - ] - }; + } } - - } } - return [@array]; + return $hash; } diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 31485bc..ef91304 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -427,3 +427,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES(' INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('UseCourseReserves', '0', 'Enable the course reserves feature.', NULL, 'YesNo'); INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacShowHoldNotes',0,'Show hold notes on OPAC','','YesNo'); INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('CalculateFinesOnReturn','1','Switch to control if overdue fines are calculated on return or not', '', 'YesNo'); +INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('MergeReportFields','','Displayed fields for deleted MARC records after merge',NULL,'Free'); diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index f3a90d8..cc97b59 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -6984,6 +6984,20 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( } + + + +$DBversion = "3.13.00.XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + $dbh->do(" + INSERT INTO systempreferences (variable,value,explanation,options,type) + VALUES('MergeReportFields','','Displayed fields for deleted MARC records after merge',NULL,'Free'); + "); + print "Upgrade to $DBversion done (Add system preference 'MergeReportFields')\n"; + SetVersion($DBversion); +} + + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref index 96f3603..a112b82 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref @@ -176,3 +176,12 @@ Cataloging: - pref: NotesBlacklist class: multi - note fields in title notes separator (OPAC record details) and in the description separator (Staff client record details). The fields should appear separated with commas and according with the Koha MARC format (eg 3.. for UNIMARC, 5.. for MARC21) + - + - pref: MergeReportFields + - "fields to display for deleted records after merge" + - "
example: '001,245ab,600'" + - "displays:" + - "