From dd3f26d50b85438f676b692d92488f014df5eb0b Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Tue, 28 May 2013 16:24:53 +0200 Subject: [PATCH 1/4] 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) --- Koha/Util/MARC.pm | 37 +-- cataloguing/merge.pl | 282 +++++++++++--------- installer/data/mysql/sysprefs.sql | 1 + installer/data/mysql/updatedatabase.pl | 11 + .../prog/en/includes/merge-record.inc | 139 +++++----- .../en/modules/admin/preferences/cataloguing.pref | 9 + .../prog/en/modules/cataloguing/merge.tt | 239 +++++++++++++---- .../prog/en/modules/virtualshelves/shelves.tt | 20 +- 8 files changed, 460 insertions(+), 278 deletions(-) diff --git a/Koha/Util/MARC.pm b/Koha/Util/MARC.pm index 5d026ee..22774b6 100644 --- a/Koha/Util/MARC.pm +++ b/Koha/Util/MARC.pm @@ -46,16 +46,11 @@ sub createMergeHash { if ( !defined($tagslib) || $tagslib->{$fieldtag}->{'@'}->{'tab'} >= 0 ) { - push @array, - { - field => [ - { - tag => $fieldtag, - key => _createKey(), - value => $field->data(), - } - ] - }; + push @array, { + tag => $fieldtag, + key => _createKey(), + value => $field->data(), + }; } } else { @@ -70,12 +65,11 @@ sub createMergeHash { && $tagslib->{$fieldtag}->{ @$subfield[0] }->{'tab'} >= 0 ) ) { - push @subfield_array, - { + push @subfield_array, { subtag => @$subfield[0], subkey => _createKey(), value => @$subfield[1], - }; + }; } } @@ -89,17 +83,12 @@ sub createMergeHash { && @subfield_array ) { - push @array, - { - field => [ - { - tag => $fieldtag, - key => _createKey(), - indicator1 => $field->indicator(1), - indicator2 => $field->indicator(2), - subfield => [@subfield_array], - } - ] + push @array, { + tag => $fieldtag, + key => _createKey(), + indicator1 => $field->indicator(1), + indicator2 => $field->indicator(2), + subfield => [@subfield_array], }; } diff --git a/cataloguing/merge.pl b/cataloguing/merge.pl index e24b767..36e5970 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; @@ -33,14 +32,14 @@ use C4::Acquisition qw/ModOrder GetOrdersByBiblionumber/; use Koha::MetadataRecord; 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, @@ -54,28 +53,41 @@ 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 $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); + 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; } @@ -87,137 +99,165 @@ if ($merge) { 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); + 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); + } - # TODO : Moving reserves + # 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 @allorders = GetOrdersByBiblionumber($biblionumber); + my @tobiblioitem = GetBiblioItemByBiblioNumber ($ref_biblionumber); my $tobiblioitem_biblioitemnumber = $tobiblioitem [0]-> {biblioitemnumber }; foreach my $myorder (@allorders) { - $myorder->{'biblionumber'} = $tobiblio; + $myorder->{'biblionumber'} = $ref_biblionumber; ModOrder ($myorder); # TODO : add error control (in ModOrder?) } - # Deleting the other record + # Deleting the other records if (scalar(@errors) == 0) { - # Move holds - MergeHolds($dbh,$tobiblio,$frombiblio); - my $error = DelBiblio($frombiblio); - push @errors, $error if ($error); + # 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); + + my $marcflavour = lc(C4::Context->preference('marcflavour')); + + # Creating a loop for display + my @records; + foreach my $biblionumber (@biblionumbers) { + my $marcrecord = GetMarcBiblio($biblionumber); + my $frameworkcode = GetFrameworkCode($biblionumber); + my $recordObj = new Koha::MetadataRecord({'record' => $marcrecord, schema => $marcflavour}); + my $record = { + biblionumber => $biblionumber, + record => $marcrecord, + frameworkcode => $frameworkcode, + display => $recordObj->createMergeHash($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 $recordObj1 = new Koha::MetadataRecord({ 'record' => GetMarcBiblio($mergereference), 'schema' => lc C4::Context->preference('marcflavour') }); - my $recordObj2 = new Koha::MetadataRecord({ 'record' => GetMarcBiblio($notreference), 'schema' => lc C4::Context->preference('marcflavour') }); - - my @record1 = $recordObj1->createMergeHash($tagslib); - my @record2 = $recordObj2->createMergeHash($tagslib); - - # Parameters - $template->param( - biblio1 => $mergereference, - biblio2 => $notreference, - mergereference => $mergereference, - record1 => @record1, - record2 => @record2, - framework => $framework, - ); } - else { + my ($biblionumbertag) = GetMarcFromKohaField('biblio.biblionumber'); + + # Parameters + $template->param( + ref_biblionumber => $ref_biblionumber, + records => \@records, + ref_record => $records[0], + 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; + } # 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, + ); } } @@ -228,11 +268,3 @@ if (@errors) { output_html_with_http_headers $input, $cookie, $template->output; exit; - -=head1 FUNCTIONS - -=cut - -# ------------------------ -# Functions -# ------------------------ diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 2c45345..b013f1f 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -182,6 +182,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('maxRecordsForFacets','20',NULL,NULL,'Integer'), ('maxreserves','50','','Define maximum number of holds a patron can place','Integer'), ('memberofinstitution','0',NULL,'If ON, patrons can be linked to institutions','YesNo'), +('MergeReportFields','',NULL,'Displayed fields for deleted MARC records after merge','Free'), ('minPasswordLength','3',NULL,'Specify the minimum length of a patron/staff password','free'), ('NewItemsDefaultLocation','','','If set, all new items will have a location of the given Location Code ( Authorized Value type LOC )',''), ('noissuescharge','5','','Define maximum amount withstanding before check outs are blocked','Integer'), diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index c8e883f..424490a 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -8012,6 +8012,17 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { SetVersion($DBversion); } +$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/includes/merge-record.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc index 81a7cc8..4752515 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc @@ -1,91 +1,102 @@ [% BLOCK sourcetab %] -
- [% IF ( records ) %] - +
-
    - [% FOREACH record IN records %] - [% FOREACH fiel IN record.field %] -
  • - [% IF (defaultrecord) %] - - [% ELSE %] - - [% END %] - [% fiel.tag %] +
      + [% FOREACH field IN record.display %] + [% IF field.tag != biblionumbertag %] +
    • + [% IF (tabrecord.reference) %] + + [% ELSE %] + + [% END %] + - - - [% IF ( fiel.value ) %] / [% fiel.value %] - - - [% END %] + + + [% IF ( field.value ) %] + / [% field.value %] + + + [% END %] - [% IF ( fiel.subfield ) %] -
        - [% FOREACH subfiel IN fiel.subfield %] -
      • - [% IF (defaultrecord) %] - - [% ELSE %] - + [% IF ( field.subfield.size ) %] +
          + [% FOREACH subfield IN field.subfield %] +
        • + [% IF (tabrecord.reference) %] + + [% ELSE %] + + [% END %] + + + +
        • + [% END %] +
        [% END %] - [% subfiel.subtag %] / [% subfiel.value %] - -
      • - [% END %] -
      + [% END %] [% END %] - [% END %] -
    • - [% END %] -
    +
- [% END %] -
+
[% END %] + [% BLOCK mergesource %]

Source records

- [% PROCESS sourcetab records=record1 recordnumber=1 defaultrecord=1 %] - [% PROCESS sourcetab records=record2 recordnumber=2 defaultrecord=0 %] + [% IF ( sourcerecords.size ) %] + [% FOREACH record IN sourcerecords %] + [% PROCESS sourcetab tabrecord=record %] + [% END %] + [% END %]
[% END %] + [% BLOCK mergetarget %]

Destination record

    - [% FOREACH record IN record1 %] - [% FOREACH fiel IN record.field %]
  • - [% fiel.tag %] - - - [% IF ( fiel.value ) %] / - [% fiel.value %] - - + [% FOREACH field IN ref_record.display %] + [% IF field.tag != biblionumbertag %] +
  • + [% field.tag %] + + + [% IF ( field.value ) %] + / [% field.value %] + + [% END %] - [% IF ( fiel.subfield ) %] -
      - [% FOREACH subfiel IN fiel.subfield %] -
    • - [% subfiel.subtag %] / [% subfiel.value %] - - -
    • - [% END %] -
    + [% IF ( field.subfield ) %] +
      + [% FOREACH subfield IN field.subfield %] +
    • + [% subfield.subtag %] / [% subfield.value %] + + +
    • + [% END %] +
    [% END %] -
  • [% END %] - [% END %] + + [% END %] + [% END %]
-
+
[% END %] 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 169aec4..b0177f2 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 @@ -182,3 +182,12 @@ Cataloging: yes: Display no: "Don't display" - acquisition details on the biblio detail page. + - + - pref: MergeReportFields + - "fields to display for deleted records after merge" + - "
example: '001,245ab,600'" + - "displays:" + - "