From 518831236b6fe4cfb1ce143344197871976a938c 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 --- C4/ImportBatch.pm | 2 +- cataloguing/merge.pl | 274 +++++++++++++++------ koha-tmpl/intranet-tmpl/prog/css/merge-record.css | 61 +++++ .../prog/en/includes/authorities_js.inc | 2 +- .../prog/en/includes/merge-record.inc | 9 +- .../prog/en/modules/cataloguing/addbooks.tt | 2 +- .../prog/en/modules/cataloguing/merge.tt | 25 +- .../prog/en/modules/tools/manage-marc-import.tt | 5 +- .../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, 343 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 4d63e8d..a454d13 100644 --- a/C4/ImportBatch.pm +++ b/C4/ImportBatch.pm @@ -1130,7 +1130,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 09517ed..8675ea3 100755 --- a/cataloguing/merge.pl +++ b/cataloguing/merge.pl @@ -29,10 +29,11 @@ use C4::Serials; use C4::Koha; use C4::Reserves qw/MergeHolds/; use C4::Acquisition qw/ModOrder GetOrdersByBiblionumber/; +use C4::ImportBatch; 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; @@ -47,17 +48,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; @@ -75,46 +100,46 @@ if ($merge) { } # Rewriting the leader - $record->leader(GetMarcBiblio($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 $itemnumbers = get_itemnumbers_of($biblionumber); - foreach my $itemnumber (@{ $itemnumbers->{$biblionumber} }) { - my $res = MoveItemFromBiblio($itemnumber, $biblionumber, $ref_biblionumber); - if (not defined $res) { - push @notmoveditems, $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 $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); + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); my %report_record = ( - biblionumber => $biblionumber, + record_num => $record_num, fields => {}, ); foreach my $field (@report_fields) { @@ -143,53 +168,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 orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) - my @allorders = GetOrdersByBiblionumber($biblionumber); - my @tobiblioitem = GetBiblioItemByBiblioNumber ($ref_biblionumber); - my $tobiblioitem_biblioitemnumber = $tobiblioitem [0]-> {biblioitemnumber }; - 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); @@ -198,19 +199,24 @@ if ($merge) { # Creating a loop for display my @records; - foreach my $biblionumber (@biblionumbers) { - my $marcrecord = GetMarcBiblio($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; @@ -221,7 +227,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, @@ -230,11 +237,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; @@ -266,4 +281,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 ); +} + +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 0000000..a6c3a47 --- /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 cf4a072..eb9d6d5 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 df79d8e..6748d73 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc @@ -4,7 +4,7 @@