From ea13bcdda352cfb13a4c8d258a6ea56a4ed11176 Mon Sep 17 00:00:00 2001 From: Jesse Weaver Date: Wed, 9 Mar 2016 17:01:25 -0700 Subject: [PATCH] Bug 14578: Allow manual merging of import records on match Test plan: 1) Ensure that normal record merging still correctly functions. 2) Import a record that matches an existing record (exporting an existing record and reimporting it is an easy way to test this). 3) On the manage import batch page, there should now be a link to Merge. 4) Clicking this and going through the merge process should leave both records in place but update the import record. Sponsored-by: Rutgers School of Law Signed-off-by: Kyle M Hall Signed-off-by: Heather Mitchell --- C4/ImportBatch.pm | 2 +- cataloguing/merge.pl | 277 ++++++++++++++------- koha-tmpl/intranet-tmpl/prog/css/merge-record.css | 61 +++++ .../prog/en/includes/authorities_js.inc | 2 +- .../prog/en/includes/merge-record.inc | 11 +- .../prog/en/modules/cataloguing/addbooks.tt | 2 +- .../prog/en/modules/cataloguing/merge.tt | 17 +- .../prog/en/modules/tools/manage-marc-import.tt | 3 +- .../prog/en/modules/virtualshelves/shelves.tt | 2 +- koha-tmpl/intranet-tmpl/prog/js/merge-record.js | 58 +++++ tools/batch_records_ajax.pl | 7 +- 11 files changed, 338 insertions(+), 104 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/css/merge-record.css diff --git a/C4/ImportBatch.pm b/C4/ImportBatch.pm index 8d39ed2034..f25c5b80c1 100644 --- a/C4/ImportBatch.pm +++ b/C4/ImportBatch.pm @@ -1121,7 +1121,7 @@ sub GetImportRecordsRange { $order_by .= " $order_by_direction, authorized_heading" if $order_by eq 'title'; - my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id, + my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id, import_records.import_batch_id, record_sequence, status, overlay_status, matched_biblionumber, matched_authid, record_type FROM import_records diff --git a/cataloguing/merge.pl b/cataloguing/merge.pl index 146e88e598..5193edc699 100755 --- a/cataloguing/merge.pl +++ b/cataloguing/merge.pl @@ -29,13 +29,13 @@ use C4::Serials; use C4::Koha; use C4::Reserves qw/MergeHolds/; use C4::Acquisition qw/ModOrder GetOrdersByBiblionumber/; - +use C4::ImportBatch; use Koha::BiblioFrameworks; use Koha::Items; use Koha::MetadataRecord; my $input = new CGI; -my @biblionumbers = $input->multi_param('biblionumber'); +my @record_paths = $input->multi_param('record'); my $merge = $input->param('merge'); my @errors; @@ -50,17 +50,41 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); +my $sources = { + catalog => { + delete => \&_delete_catalog, + fetch => \&_fetch_catalog, + get_link => \&_get_link_catalog, + get_preview_link => \&_get_preview_link_catalog, + merge_other => \&_merge_other_catalog, + update => \&_update_catalog, + }, + batch => { + fetch => \&_fetch_import_batch, + get_link => \&_get_preview_link_import_batch, + get_preview_link => \&_get_preview_link_import_batch, + update => \&_update_import_batch, + }, +}; + +sub split_record_path { + my ($source, $num) = split /\//, shift, 2; + $source =~ s/-.*//; + + return ( $source, $num ); +} + #------------------------ # Merging #------------------------ if ($merge) { - my $dbh = C4::Context->dbh; - # Creating a new record from the html code - my $record = TransformHtmlToMarc( $input, 1 ); - my $ref_biblionumber = $input->param('ref_biblionumber'); - @biblionumbers = grep { $_ != $ref_biblionumber } @biblionumbers; + my $record = TransformHtmlToMarc( $input ); + my $ref_record_path = $input->param('ref_record'); + + my ( $ref_record_source, $ref_record_num ) = split_record_path $ref_record_path; + my @other_record_paths = map +[ split_record_path $_ ], grep { $_ ne $ref_record_path } @record_paths; # prepare report my @report_records; @@ -78,49 +102,46 @@ if ($merge) { } # Rewriting the leader - $record->leader(GetMarcBiblio({ biblionumber => $ref_biblionumber })->leader()); + my $orig_ref_record = $sources->{$ref_record_source}->{fetch}($ref_record_num); + $record->leader( $orig_ref_record->leader() ); my $frameworkcode = $input->param('frameworkcode'); my @notmoveditems; # Modifying the reference record - ModBiblio($record, $ref_biblionumber, $frameworkcode); - - # Moving items from the other record to the reference record - foreach my $biblionumber (@biblionumbers) { - my $items = Koha::Items->search({ biblionumber => $biblionumber }); - while ( my $item = $items->next) { - my $res = MoveItemFromBiblio( $item->itemnumber, $biblionumber, $ref_biblionumber ); - if ( not defined $res ) { - push @notmoveditems, $item->itemnumber; + $sources->{$ref_record_source}->{update}( $record, $ref_record_num, $frameworkcode ); + + # Are all the records from the same source? + my $all_records_same_source = 1; + + # Merging in information from other records + foreach my $record_path (@other_record_paths) { + my ( $record_source, $record_num ) = @$record_path; + + if ( $record_source eq $ref_record_source ) { + # We only want to merge in information from the other records if they're the same kind. + # + # We can't merge serials from an import bib to a catalog bib, for instance. + my @record_errors = $sources->{$record_source}->{merge_other}( $record_num, $ref_record_num ); + + if ( @record_errors ) { + push @errors, @record_errors; + } else { + push @errors, $sources->{$record_source}->{delete}( $record_num ); } + } else { + $all_records_same_source = 0; } } - # If some items could not be moved : - if (scalar(@notmoveditems) > 0) { - my $itemlist = join(' ',@notmoveditems); - push @errors, { code => "CANNOT_MOVE", value => $itemlist }; - } - - 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 $sth_suggestions = $dbh->prepare(" - UPDATE suggestions SET biblionumber = ? WHERE biblionumber = ? - "); my $report_header = {}; - foreach my $biblionumber ($ref_biblionumber, @biblionumbers) { + foreach my $record_path ( [ $ref_record_source, $ref_record_num ], @other_record_paths ) { + my ( $record_source, $record_num ) = @$record_path; + # build report - my $marcrecord = GetMarcBiblio({ biblionumber => $biblionumber }); + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); my %report_record = ( - biblionumber => $biblionumber, + record_num => $record_num, fields => {}, ); foreach my $field (@report_fields) { @@ -149,54 +170,29 @@ if ($merge) { push @report_records, \%report_record; } - 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 serials - $sth_serial->execute($ref_biblionumber, $biblionumber); - - # Moving suggestions - $sth_suggestions->execute($ref_biblionumber, $biblionumber); - - # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) - my @allorders = GetOrdersByBiblionumber($biblionumber); - foreach my $myorder (@allorders) { - $myorder->{'biblionumber'} = $ref_biblionumber; - ModOrder ($myorder); - # TODO : add error control (in ModOrder?) - } - - # 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( + all_records_same_source => $all_records_same_source, result => 1, report_records => \@report_records, report_header => $report_header, - ref_biblionumber => scalar $input->param('ref_biblionumber') + ref_record_path => scalar $input->param('ref_record'), + ref_record_id => "$ref_record_source-$ref_record_num", + ref_record_link => $sources->{$ref_record_source}->{get_link}($ref_record_num), ); #------------------------- # Show records to merge #------------------------- } else { - my $ref_biblionumber = $input->param('ref_biblionumber'); + my $ref_record_path = $input->param('ref_record'); + + if ($ref_record_path) { + my ( $ref_record_source, $ref_record_num ) = split_record_path $ref_record_path; - if ($ref_biblionumber) { my $framework = $input->param('frameworkcode'); - $framework //= GetFrameworkCode($ref_biblionumber); + $framework //= GetFrameworkCode($ref_record_num) if $ref_record_source eq 'catalog'; + $framework //= ''; # Getting MARC Structure my $tagslib = GetMarcStructure(1, $framework); @@ -205,19 +201,24 @@ if ($merge) { # Creating a loop for display my @records; - foreach my $biblionumber (@biblionumbers) { - my $marcrecord = GetMarcBiblio({ biblionumber => $biblionumber }); - my $frameworkcode = GetFrameworkCode($biblionumber); + foreach my $record_path ( $input->param('record') ) { + my ( $record_source, $record_num ) = split_record_path $record_path; + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); + # FIXME: We assume the default framework for import records. + my $frameworkcode = $record_source eq 'catalog' ? GetFrameworkCode($record_num) : ''; my $recordObj = new Koha::MetadataRecord({'record' => $marcrecord, schema => $marcflavour}); my $record = { - recordid => $biblionumber, + record_path => $record_path, + recordid => "$record_source-$record_num", + record_source => $record_source, + record_num => $record_num, record => $marcrecord, frameworkcode => $frameworkcode, display => $recordObj->createMergeHash($tagslib), }; - if ($ref_biblionumber and $ref_biblionumber == $biblionumber) { + if ($ref_record_path and $ref_record_path eq $record_path) { $record->{reference} = 1; - $template->param(ref_record => $record); + $template->param(ref_record_path => $record); unshift @records, $record; } else { push @records, $record; @@ -228,7 +229,8 @@ if ($merge) { # Parameters $template->param( - ref_biblionumber => $ref_biblionumber, + ref_record_path => $ref_record_path, + ref_recordid => "$ref_record_source-$ref_record_num", records => \@records, ref_record => $records[0], framework => $framework, @@ -237,11 +239,19 @@ if ($merge) { ); } else { my @records; - foreach my $biblionumber (@biblionumbers) { - my $frameworkcode = GetFrameworkCode($biblionumber); + foreach my $record_path ( $input->multi_param('record') ) { + my ( $record_source, $record_num ) = split_record_path $record_path; + + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); + # FIXME: We assume the default framework for import records. + my $frameworkcode = $record_source eq 'catalog' ? GetFrameworkCode($record_num) : ''; my $record = { - biblionumber => $biblionumber, - data => GetBiblioData($biblionumber), + record_path => $record_path, + recordid => "$record_path-$record_num", + record_source => $record_source, + record_num => $record_num, + preview_link => $sources->{$record_source}->{get_preview_link}($record_num), + data => TransformMarcToKoha( $marcrecord, $frameworkcode ), frameworkcode => $frameworkcode, }; push @records, $record; @@ -263,4 +273,101 @@ if (@errors) { } output_html_with_http_headers $input, $cookie, $template->output; -exit; + +sub _delete_catalog { + my ( $biblionumber ) = @_; + return DelBiblio($biblionumber); +} + +sub _fetch_catalog { + my ( $biblionumber ) = @_; + + return GetMarcBiblio({ biblionumber => $biblionumber }); +} + +sub _get_link_catalog { + my ( $biblionumber ) = @_; + + return "/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=$biblionumber"; +} + +sub _get_preview_link_catalog { + my ( $biblionumber ) = @_; + + return "/cgi-bin/koha/catalogue/showmarc.pl?id=$biblionumber"; +} + +sub _merge_other_catalog { + my ( $biblionumber, $ref_biblionumber ) = @_; + + my $dbh = C4::Context->dbh; + my @errors; + + my @notmoveditems; + my $itemnumbers = get_itemnumbers_of($biblionumber); + foreach my $itemnumber (@{ $itemnumbers->{$biblionumber} }) { + if ( !MoveItemFromBiblio( $itemnumber, $biblionumber, $ref_biblionumber ) ) { + 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 }; + } + + 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 = ? + "); + + # 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 serials + $sth_serial->execute($ref_biblionumber, $biblionumber); + + # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) + foreach my $myorder( GetOrdersByBiblionumber($biblionumber) ) { + $myorder->{'biblionumber'} = $ref_biblionumber; + ModOrder ($myorder); + # TODO : add error control (in ModOrder?) + } + + if (scalar(@errors) == 0) { + MergeHolds($dbh, $ref_biblionumber, $biblionumber); + } + + return @errors; +} + +sub _update_catalog { + my ( $record, $biblionumber, $frameworkcode ) = @_; + ModBiblio( $record, $biblionumber, $frameworkcode ); +} + +sub _fetch_import_batch { + my ( $import_recordid ) = @_; + return C4::ImportBatch::GetRecordFromImportBiblio( $import_recordid ); +} + +sub _get_preview_link_import_batch { + my ( $import_recordid ) = @_; + + return "/cgi-bin/koha/catalogue/showmarc.pl?importid=$import_recordid"; +} + +sub _update_import_batch { + my ( $record, $import_recordid ) = @_; + ModBiblioInBatch( $import_recordid, $record ); +} diff --git a/koha-tmpl/intranet-tmpl/prog/css/merge-record.css b/koha-tmpl/intranet-tmpl/prog/css/merge-record.css new file mode 100644 index 0000000000..a6c3a479db --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/css/merge-record.css @@ -0,0 +1,61 @@ +.merge-record-fixed-contents { + display: inline-block; +} + +.merge-record-fixed-contents, .merge-record-subfield { + padding: 2px; +} + +.merge-record-unique-0, #tabs .merge-record-unique-0 { + background-color: #005AC8; + color: white; +} + +.merge-record-unique-1, #tabs .merge-record-unique-1 { + background-color: #AA0A3C; + color: white; +} + +.merge-record-unique-2, #tabs .merge-record-unique-2 { + background-color: #F0F032; +} + +.merge-record-unique-3, #tabs .merge-record-unique-3 { + background-color: #00A0FA; + color: white; +} + +.merge-record-unique-4, #tabs .merge-record-unique-4 { + background-color: #FA78FA; +} + +.merge-record-unique-5, #tabs .merge-record-unique-5 { + background-color: #14D2DC; +} + +.merge-record-unique-6, #tabs .merge-record-unique-6 { + background-color: #8214A0; + color: white; +} + +.merge-record-unique-7, #tabs .merge-record-unique-7 { + background-color: #FA7850; +} + +.merge-record-unique-8, #tabs .merge-record-unique-8 { + background-color: #0AB45A; + color: white; +} + +.merge-record-unique-9, #tabs .merge-record-unique-9 { + background-color: #006E82; + color: white; +} + +.merge-record-unique-10, #tabs .merge-record-unique-10 { + background-color: #A0FA82; +} + +.merge-record-unique-11, #tabs .merge-record-unique-11 { + background-color: #F9E5BD; +} diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc index 7109dc94ca..615e1eeb50 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc @@ -9,7 +9,7 @@ function mergeAuth(authid, summary) { if (typeof alreadySelected.mergereference !== 'undefined') { refstring = "&mergereference=" + alreadySelected.mergereference; } - window.location.href = "/cgi-bin/koha/authorities/merge.pl?authid=" + authid + "&authid=" + alreadySelected.authid + refstring; + window.location.href = "/cgi-bin/koha/authorities/merge.pl?record=catalog/" + authid + "&record=catalog/" + alreadySelected.authid + refstring; } else { $.cookie('auth_to_merge', JSON.stringify({ 'authid': authid, 'summary': summary }), { 'path' : '/' }); showMergingInProgress(); 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 6d14015916..dc93ad3e33 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc @@ -4,8 +4,8 @@
    [% FOREACH field IN record.display %] [% IF field.tag != biblionumbertag %] -
  • - [% IF (tabrecord.reference) %] +
  • + [% IF (record.reference) %] [% ELSE %] @@ -15,7 +15,7 @@ [% IF ( field.value ) %] - / [% field.value | html %] + / [% field.value | html %] [% END %] @@ -23,7 +23,7 @@ [% IF ( field.subfield.size ) %]
      [% FOREACH subfield IN field.subfield %] -
    • +
    • [% IF (tabrecord.reference) %] [% ELSE %] @@ -51,7 +51,8 @@ [% FOREACH record IN sourcerecords %]
    • - [% record.recordid | html %] + [% IF record.record_source == 'batch' %]import[% END %] + #[% record.record_num | html %] [% IF record.reference %](ref)[% END %]
    • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt index 46157389b8..8afacc2e1c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbooks.tt @@ -281,7 +281,7 @@ } else { var params = []; $(checkboxes).each(function() { - params.push('biblionumber=' + $(this).val()); + params.push('record=catalog/' + $(this).val()); }); var url = '/cgi-bin/koha/cataloguing/merge.pl?' + params.join('&'); location.href = url; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/merge.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/merge.tt index c21baca652..f1d7e25958 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/merge.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/merge.tt @@ -5,6 +5,7 @@ [% INCLUDE 'doc-head-open.inc' %] Koha › Cataloging › Merging records +[% Asset.css("css/merge-record.css") | $raw %] [% INCLUDE 'doc-head-close.inc' %]