View | Details | Raw Unified | Return to bug 33036
Collapse All | Expand All

(-)a/Koha/Biblio.pm (+88 lines)
Lines 24-29 use URI; Link Here
24
use URI::Escape qw( uri_escape_utf8 );
24
use URI::Escape qw( uri_escape_utf8 );
25
25
26
use C4::Koha qw( GetNormalizedISBN GetNormalizedUPC GetNormalizedOCLCNumber );
26
use C4::Koha qw( GetNormalizedISBN GetNormalizedUPC GetNormalizedOCLCNumber );
27
use C4::Biblio qw( DelBiblio AddBiblio ModBiblio );
28
use C4::Serials qw( CountSubscriptionFromBiblionumber );
29
use C4::Serials qw( CountSubscriptionFromBiblionumber );
30
use C4::Reserves qw( MergeHolds );
31
use C4::Acquisition qw( ModOrder GetOrdersByBiblionumber );
27
32
28
use Koha::Database;
33
use Koha::Database;
29
use Koha::DateUtils qw( dt_from_string );
34
use Koha::DateUtils qw( dt_from_string );
Lines 1746-1751 sub opac_summary_html { Link Here
1746
    return $summary_html;
1751
    return $summary_html;
1747
}
1752
}
1748
1753
1754
=head3 merge_with
1755
1756
    my $biblio = Koha::Biblios->find($biblionumber);
1757
    $biblio->merge_with(\@biblio_ids);
1758
1759
    This subroutine merges a list of bibliographic records  into the bibliographic record.
1760
    This function DOES NOT CHANGE the bibliographic metadata of the record. But it links all
1761
    items, holds, subscriptions, serials issues and article_requests to the record. After doing changes
1762
    bibliographic records listed are deleted
1763
1764
=cut
1765
1766
sub merge_with {
1767
    my ( $self, $biblio_ids ) = @_;
1768
    my @biblio_ids_to_merge = @{$biblio_ids};
1769
    my $dbh                 = C4::Context->dbh;
1770
    my $ref_biblionumber    = $self->biblionumber;
1771
    # Ensure the keeper isn't in the list of records to merge
1772
    @biblio_ids_to_merge = grep { $_ ne $ref_biblionumber } @biblio_ids_to_merge;
1773
    my @worked_ids = ();
1774
    my %results    = ( 'biblio_id' => $ref_biblionumber, 'merged_ids' => \@worked_ids, 'error' => '', 'status' => 1 );
1775
    #my $biblio = Koha::Biblios->find($self->biblionumber);
1776
1777
    foreach (@biblio_ids_to_merge) {
1778
        my $bn_merge = $_;
1779
        eval {
1780
            my $from_biblio = Koha::Biblios->find($bn_merge);
1781
            $from_biblio->items->move_to_biblio($self);
1782
            $from_biblio->article_requests->update( { biblionumber => $ref_biblionumber }, { no_triggers => 1 } );
1783
1784
            # Moving subscriptions from the other record to the reference record
1785
            my $sth_subscription = $dbh->prepare( "
1786
             UPDATE subscription SET biblionumber = ? WHERE biblionumber = ?
1787
            " );
1788
            my $sth_subscriptionhistory = $dbh->prepare( "
1789
             UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ?
1790
            " );
1791
            my $subcount = CountSubscriptionFromBiblionumber($bn_merge);
1792
            if ( $subcount > 0 ) {
1793
                $sth_subscription->execute( $ref_biblionumber, $bn_merge );
1794
                $sth_subscriptionhistory->execute( $ref_biblionumber, $bn_merge );
1795
            }
1796
1797
            # Moving serials
1798
            my $sth_serial = $dbh->prepare( "
1799
             UPDATE serial SET biblionumber = ? WHERE biblionumber = ?
1800
            " );
1801
            $sth_serial->execute( $ref_biblionumber, $bn_merge );
1802
1803
            # Moving suggestions
1804
            my $sth_suggestions = $dbh->prepare( "
1805
             UPDATE suggestions SET biblionumber = ? WHERE biblionumber = ?
1806
            " );
1807
            $sth_suggestions->execute( $ref_biblionumber, $bn_merge );
1808
            # Moving orders (orders linked to items of frombiblio have already been moved by move_to_biblio)
1809
            my @allorders = GetOrdersByBiblionumber($bn_merge);
1810
            foreach my $myorder (@allorders) {
1811
                $myorder->{'biblionumber'} = $ref_biblionumber;
1812
                ModOrder($myorder);
1813
                # TODO : add error control (in ModOrder?)
1814
            }
1815
1816
            # Move holds
1817
            MergeHolds( $dbh, $ref_biblionumber, $bn_merge );
1818
        };
1819
        if ($@) {
1820
            $results{'error'}  = $@;
1821
            $results{'status'} = 0;
1822
            return \%results;
1823
        }
1824
        # Deleting record
1825
        my $error = DelBiblio($bn_merge);
1826
        if ($error) {
1827
            $results{'error'}  = $error;
1828
            $results{'status'} = 0;
1829
            return \%results;
1830
        } else {
1831
            push( @worked_ids, $bn_merge );
1832
        }
1833
    }
1834
    return \%results;
1835
}
1836
1749
=head2 Internal methods
1837
=head2 Internal methods
1750
1838
1751
=head3 type
1839
=head3 type
(-)a/Koha/REST/V1/Biblios.pm (+60 lines)
Lines 860-863 sub list { Link Here
860
    };
860
    };
861
}
861
}
862
862
863
=head3 merge
864
865
Controller function that handles merging two biblios. If an optional
866
MARCXML is provided as the request body, this MARCXML replaces the
867
bibliodata of the merge target biblio. Syntax format inside the request body
868
must match with the Marc format used into Koha installation (MARC21 or UNIMARC)
869
870
=cut
871
872
sub merge {
873
    my $c                = shift->openapi->valid_input or return;
874
    my $ref_biblionumber = $c->param('biblio_id');
875
    my $bn_merge         = $c->param('biblio_id_to_merge');
876
    my $dbh              = C4::Context->dbh;
877
878
    my $body   = $c->req->body;
879
    my $biblio = Koha::Biblios->find($ref_biblionumber);
880
    if ( not defined $biblio ) {
881
        return $c->render(
882
            status => 404,
883
            json   => { error => sprintf( "[%s] biblio to merge into not found", $ref_biblionumber ) }
884
        );
885
    }
886
887
    my $from_biblio = Koha::Biblios->find($bn_merge);
888
    if ( not defined $from_biblio ) {
889
        return $c->render(
890
            status => 404,
891
            # openapi => { error => sprintf("[%s] biblio to merge from not found", $bn_merge) }
892
            json => { error => sprintf( "[%s] biblio to merge from not found", $bn_merge ) }
893
        );
894
    }
895
    my $results;
896
    eval {
897
        if ($body) {
898
            my $record = MARC::Record->new_from_xml( $body, 'UTF-8', C4::Context->preference("marcflavour") );
899
            $record->leader( $biblio->metadata->record->leader() );
900
            ModBiblio( $record, $ref_biblionumber, $biblio->frameworkcode );
901
        }
902
        my @biblio_ids_to_merge = ($bn_merge);
903
        $results = $biblio->merge_with( \@biblio_ids_to_merge );
904
    };
905
    if ($@) {
906
        return $c->render( status => 400, json => { error => $@ } );
907
    } else {
908
        if ( $results->{'status'} == 1 ) {
909
            return $c->render(
910
                status  => 200,
911
                openapi => {
912
                    message => sprintf( "Successfully merged %s into %s", $bn_merge, $ref_biblionumber ),
913
                    merged  => $bn_merge,
914
                    kept    => $ref_biblionumber,
915
                }
916
            );
917
        } else {
918
            return $c->render( status => 400, json => { error => $results->{'error'} } );
919
        }
920
    }
921
}
922
863
1;
923
1;
(-)a/api/v1/swagger/paths/biblios_merge.yaml (+47 lines)
Line 0 Link Here
1
"/biblios/{biblio_id}/merge/{biblio_id_to_merge}":
2
  post:
3
    x-mojo-to: Biblios#merge
4
    operationId: mergeBiblios
5
    tags:
6
        - merge_biblios
7
    summary: Merge Biblios
8
    parameters:
9
      - name: biblio_id
10
        in: path
11
        description: Bilblionumber to be merged into
12
        required: true
13
        type: string
14
      - name: biblio_id_to_merge
15
        in: path
16
        required: true
17
        description: Biblionumber from which to merge
18
        type: string
19
      - $ref: "../swagger.yaml#/parameters/biblio_id_pp"
20
      - $ref: "../swagger.yaml#/parameters/framework_id_header"
21
      - $ref: "../swagger.yaml#/parameters/marc_schema_header"
22
    responses:
23
      '200':
24
        description: Result message
25
      '404':
26
        description: Biblio not found
27
        schema:
28
          "$ref": "../swagger.yaml#/definitions/error"
29
      '401':
30
        description: Authentication required
31
        schema:
32
          "$ref": "../swagger.yaml#/definitions/error"
33
      '403':
34
        description: Access forbidden
35
        schema:
36
          "$ref": "../swagger.yaml#/definitions/error"
37
      '500':
38
        description: Internal server error
39
        schema:
40
          "$ref": "../swagger.yaml#/definitions/error"
41
      '503':
42
        description: Under maintenance
43
        schema:
44
          "$ref": "../swagger.yaml#/definitions/error"
45
    x-koha-authorization:
46
      permissions:
47
        catalogue: "editcatalogue"
(-)a/api/v1/swagger/swagger.yaml (+2 lines)
Lines 207-212 paths: Link Here
207
    $ref: "./paths/biblios_item_groups.yaml#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items"
207
    $ref: "./paths/biblios_item_groups.yaml#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items"
208
  "/biblios/{biblio_id}/item_groups/{item_group_id}/items/{item_id}":
208
  "/biblios/{biblio_id}/item_groups/{item_group_id}/items/{item_id}":
209
    $ref: "./paths/biblios_item_groups.yaml#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items~1{item_id}"
209
    $ref: "./paths/biblios_item_groups.yaml#/~1biblios~1{biblio_id}~1item_groups~1{item_group_id}~1items~1{item_id}"
210
  "/biblios/{biblio_id}/merge/{biblio_id_to_merge}":
211
    $ref: "./paths/biblios_merge.yaml#/~1biblios~1{biblio_id}~1merge~1{biblio_id_to_merge}"
210
  "/cash_registers/{cash_register_id}/cashups":
212
  "/cash_registers/{cash_register_id}/cashups":
211
    $ref: "./paths/cash_registers.yaml#/~1cash_registers~1{cash_register_id}~1cashups"
213
    $ref: "./paths/cash_registers.yaml#/~1cash_registers~1{cash_register_id}~1cashups"
212
  "/cashups/{cashup_id}":
214
  "/cashups/{cashup_id}":
(-)a/cataloguing/merge.pl (-52 / +11 lines)
Lines 86-118 if ($merge) { Link Here
86
    # Rewriting the leader
86
    # Rewriting the leader
87
    my $biblio = Koha::Biblios->find($ref_biblionumber);
87
    my $biblio = Koha::Biblios->find($ref_biblionumber);
88
    $record->leader($biblio->metadata->record->leader());
88
    $record->leader($biblio->metadata->record->leader());
89
89
    #Take new framework code
90
    my $frameworkcode = $input->param('frameworkcode');
90
    my $frameworkcode = $input->param('frameworkcode');
91
92
    # Modifying the reference record
91
    # Modifying the reference record
93
    ModBiblio($record, $ref_biblionumber, $frameworkcode);
92
    ModBiblio($record, $ref_biblionumber, $frameworkcode);
94
93
95
    # Moving items and article requests from the other record to the reference record
96
    $biblio = $biblio->get_from_storage;
97
    foreach my $biblionumber (@biblionumbers) {
98
        my $from_biblio = Koha::Biblios->find($biblionumber);
99
        $from_biblio->items->move_to_biblio($biblio);
100
        $from_biblio->article_requests->update({ biblionumber => $ref_biblionumber }, { no_triggers => 1 });
101
    }
102
103
    my $sth_subscription = $dbh->prepare("
104
        UPDATE subscription SET biblionumber = ? WHERE biblionumber = ?
105
    ");
106
    my $sth_subscriptionhistory = $dbh->prepare("
107
        UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ?
108
    ");
109
    my $sth_serial = $dbh->prepare("
110
        UPDATE serial SET biblionumber = ? WHERE biblionumber = ?
111
    ");
112
    my $sth_suggestions = $dbh->prepare("
113
        UPDATE suggestions SET biblionumber = ? WHERE biblionumber = ?
114
    ");
115
116
    my $report_header = {};
94
    my $report_header = {};
117
    foreach my $biblionumber ($ref_biblionumber, @biblionumbers) {
95
    foreach my $biblionumber ($ref_biblionumber, @biblionumbers) {
118
        # build report
96
        # build report
Lines 148-184 if ($merge) { Link Here
148
        push @report_records, \%report_record;
126
        push @report_records, \%report_record;
149
    }
127
    }
150
128
151
    foreach my $biblionumber (@biblionumbers) {
129
    my $rmerge;
152
        # Moving subscriptions from the other record to the reference record
130
    eval {
153
        my $subcount = CountSubscriptionFromBiblionumber($biblionumber);
131
        my $newbiblio = Koha::Biblios->find($ref_biblionumber);
154
        if ($subcount > 0) {
132
        $rmerge = $newbiblio->merge_with(\@biblionumbers);
155
            $sth_subscription->execute($ref_biblionumber, $biblionumber);
133
    };
156
            $sth_subscriptionhistory->execute($ref_biblionumber, $biblionumber);
134
    if($@)
135
    {   push @errors, $@;
136
    }   else
137
    {   if ($rmerge->{'status'}==0)
138
        {   push @errors, $rmerge->{'error'};
157
        }
139
        }
158
159
    # Moving serials
160
    $sth_serial->execute($ref_biblionumber, $biblionumber);
161
162
    # Moving suggestions
163
    $sth_suggestions->execute($ref_biblionumber, $biblionumber);
164
165
    # Moving orders (orders linked to items of frombiblio have already been moved by move_to_biblio)
166
    my @allorders = GetOrdersByBiblionumber($biblionumber);
167
    foreach my $myorder (@allorders) {
168
        $myorder->{'biblionumber'} = $ref_biblionumber;
169
        ModOrder ($myorder);
170
    # TODO : add error control (in ModOrder?)
171
    }
172
173
    # Deleting the other records
174
    if (scalar(@errors) == 0) {
175
        # Move holds
176
        MergeHolds($dbh, $ref_biblionumber, $biblionumber);
177
        my $error = DelBiblio($biblionumber);
178
        push @errors, $error if ($error);
179
    }
140
    }
180
}
181
182
    # Parameters
141
    # Parameters
183
    $template->param(
142
    $template->param(
184
        result => 1,
143
        result => 1,
(-)a/t/db_dependent/Koha/Biblio.t (-3 / +29 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 30;
20
use Test::More tests => 31;
21
use Test::Exception;
21
use Test::Exception;
22
use Test::Warn;
22
use Test::Warn;
23
23
Lines 486-498 subtest 'to_api() tests' => sub { Link Here
486
    $schema->storage->txn_rollback;
486
    $schema->storage->txn_rollback;
487
};
487
};
488
488
489
subtest 'merge of records' => sub {
490
    plan tests => 1;
491
    $schema->storage->txn_begin;
492
493
    #Three biblio
494
    my $biblio1 = $builder->build_sample_biblio( { title => 'Title number 1' } );
495
    my $biblio2 = $builder->build_sample_biblio( { title => 'Title number 2' } );
496
    my $biblio3 = $builder->build_sample_biblio( { title => 'Title number 3' } );
497
498
    # One items each
499
    my $item1_1 = $builder->build_sample_item( { biblionumber => $biblio1->biblionumber, barcode => 'bar11' } );
500
    my $item2_1 = $builder->build_sample_item( { biblionumber => $biblio2->biblionumber, barcode => 'bar22' } );
501
    my $item3_1 = $builder->build_sample_item( { biblionumber => $biblio3->biblionumber, barcode => 'bar33' } );
502
    my $results = '';
503
    my @to_merge = ( $biblio2->biblionumber, $biblio3->biblionumber );
504
    eval { $results = $biblio1->merge_with( \@to_merge ); };
505
    if($@) {
506
        is( 0, 1, "Not working. Error: " . $@ );
507
    } else {
508
        my $items = $biblio1->items;
509
        is( $items->count, 3, "After merge we have 3 items on first record" );
510
    }
511
    #End work
512
    #say Dumper $results;
513
    $schema->storage->txn_rollback;
514
};
515
489
subtest 'suggestions() tests' => sub {
516
subtest 'suggestions() tests' => sub {
490
517
491
    plan tests => 3;
518
    plan tests => 3;
492
519
493
    $schema->storage->txn_begin;
520
    $schema->storage->txn_begin;
494
521
495
    my $biblio     = $builder->build_sample_biblio();
522
    my $biblio = $builder->build_sample_biblio();
496
523
497
    is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
524
    is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
498
525
499
- 

Return to bug 33036