Bug 33036: REST API: Merge biblio records implements merging of records
[koha.git] / Koha / REST / V1 / Biblios.pm
1 package Koha::REST::V1::Biblios;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Mojo::Base 'Mojolicious::Controller';
21
22 use Koha::Biblios;
23 use Koha::DateUtils;
24 use Koha::Ratings;
25 use Koha::RecordProcessor;
26 use C4::Biblio qw( DelBiblio AddBiblio ModBiblio );
27 use C4::Search qw( FindDuplicate );
28
29 use C4::Barcodes::ValueBuilder;
30 use C4::Context;
31
32 use Koha::Items;
33
34 use List::MoreUtils qw( any );
35 use MARC::Record::MiJ;
36
37 use Try::Tiny qw( catch try );
38 use JSON qw( decode_json );
39
40 =head1 API
41
42 =head2 Methods
43
44 =head3 get
45
46 Controller function that handles retrieving a single biblio object
47
48 =cut
49
50 sub get {
51     my $c = shift->openapi->valid_input or return;
52
53     my $attributes;
54     $attributes = { prefetch => [ 'metadata' ] } # don't prefetch metadata if not needed
55         unless $c->req->headers->accept =~ m/application\/json/;
56
57     my $biblio = Koha::Biblios->find( { biblionumber => $c->param('biblio_id') }, $attributes );
58
59     unless ( $biblio ) {
60         return $c->render(
61             status  => 404,
62             openapi => {
63                 error => "Object not found."
64             }
65         );
66     }
67
68     return try {
69
70         if ( $c->req->headers->accept =~ m/application\/json/ ) {
71             return $c->render(
72                 status => 200,
73                 json   => $biblio->to_api
74             );
75         }
76         else {
77             my $metadata = $biblio->metadata;
78             my $record   = $metadata->record;
79             my $schema   = $metadata->schema // C4::Context->preference("marcflavour");
80
81             $c->respond_to(
82                 marcxml => {
83                     status => 200,
84                     format => 'marcxml',
85                     text   => $record->as_xml_record($schema),
86                 },
87                 mij => {
88                     status => 200,
89                     format => 'mij',
90                     data   => $record->to_mij
91                 },
92                 marc => {
93                     status => 200,
94                     format => 'marc',
95                     text   => $record->as_usmarc
96                 },
97                 txt => {
98                     status => 200,
99                     format => 'text/plain',
100                     text   => $record->as_formatted
101                 },
102                 any => {
103                     status  => 406,
104                     openapi => [
105                         "application/json",
106                         "application/marcxml+xml",
107                         "application/marc-in-json",
108                         "application/marc",
109                         "text/plain"
110                     ]
111                 }
112             );
113         }
114     }
115     catch {
116         $c->unhandled_exception($_);
117     };
118 }
119
120 =head3 delete
121
122 Controller function that handles deleting a biblio object
123
124 =cut
125
126 sub delete {
127     my $c = shift->openapi->valid_input or return;
128
129     my $biblio = Koha::Biblios->find( $c->param('biblio_id') );
130
131     if ( not defined $biblio ) {
132         return $c->render(
133             status  => 404,
134             openapi => { error => "Object not found" }
135         );
136     }
137
138     return try {
139         my $error = DelBiblio( $biblio->id );
140
141         if ($error) {
142             return $c->render(
143                 status  => 409,
144                 openapi => { error => $error }
145             );
146         }
147         else {
148             return $c->render( status => 204, openapi => "" );
149         }
150     }
151     catch {
152         $c->unhandled_exception($_);
153     };
154 }
155
156 =head3 get_public
157
158 Controller function that handles retrieving a single biblio object
159
160 =cut
161
162 sub get_public {
163     my $c = shift->openapi->valid_input or return;
164
165     my $biblio = Koha::Biblios->find(
166         { biblionumber => $c->param('biblio_id') },
167         { prefetch     => ['metadata'] } );
168
169     unless ($biblio) {
170         return $c->render(
171             status  => 404,
172             openapi => {
173                 error => "Object not found."
174             }
175         );
176     }
177
178     return try {
179
180         my $metadata = $biblio->metadata;
181         my $record   = $metadata->record;
182
183         my $opachiddenitems_rules = C4::Context->yaml_preference('OpacHiddenItems');
184         my $patron = $c->stash('koha.user');
185
186         # Check if the biblio should be hidden for unprivileged access
187         # unless there's a logged in user, and there's an exception for it's
188         # category
189         unless ( $patron and $patron->category->override_hidden_items ) {
190             if ( $biblio->hidden_in_opac({ rules => $opachiddenitems_rules }) )
191             {
192                 return $c->render(
193                     status  => 404,
194                     openapi => {
195                         error => "Object not found."
196                     }
197                 );
198             }
199         }
200
201         my $schema = $metadata->schema // C4::Context->preference("marcflavour");
202
203         my $record_processor = Koha::RecordProcessor->new({
204             filters => 'ViewPolicy',
205             options => {
206                 interface => 'opac',
207                 frameworkcode => $biblio->frameworkcode
208             }
209         });
210         # Apply framework's filtering to MARC::Record object
211         $record_processor->process($record);
212
213         $c->respond_to(
214             marcxml => {
215                 status => 200,
216                 format => 'marcxml',
217                 text   => $record->as_xml_record($schema),
218             },
219             mij => {
220                 status => 200,
221                 format => 'mij',
222                 data   => $record->to_mij
223             },
224             marc => {
225                 status => 200,
226                 format => 'marc',
227                 text   => $record->as_usmarc
228             },
229             txt => {
230                 status => 200,
231                 format => 'text/plain',
232                 text   => $record->as_formatted
233             },
234             any => {
235                 status  => 406,
236                 openapi => [
237                     "application/marcxml+xml",
238                     "application/marc-in-json",
239                     "application/marc",
240                     "text/plain"
241                 ]
242             }
243         );
244     }
245     catch {
246         $c->unhandled_exception($_);
247     };
248 }
249
250
251 =head3 get_bookings
252
253 Controller function that handles retrieving biblio's bookings
254
255 =cut
256
257 sub get_bookings {
258     my $c = shift->openapi->valid_input or return;
259
260     my $biblio = Koha::Biblios->find( { biblionumber => $c->param('biblio_id') }, { prefetch => ['bookings'] } );
261
262     unless ($biblio) {
263         return $c->render(
264             status  => 404,
265             openapi => { error => "Object not found." }
266         );
267     }
268
269     return try {
270
271         my $bookings_rs = $biblio->bookings;
272         my $bookings    = $c->objects->search($bookings_rs);
273         return $c->render(
274             status  => 200,
275             openapi => $bookings
276         );
277     } catch {
278         $c->unhandled_exception($_);
279     };
280 }
281
282 =head3 get_items
283
284 Controller function that handles retrieving biblio's items
285
286 =cut
287
288 sub get_items {
289     my $c = shift->openapi->valid_input or return;
290
291     my $biblio        = Koha::Biblios->find( { biblionumber => $c->param('biblio_id') }, { prefetch => ['items'] } );
292     my $bookable_only = $c->param('bookable');
293
294     unless ($biblio) {
295         return $c->render(
296             status  => 404,
297             openapi => { error => "Object not found." }
298         );
299     }
300
301     return try {
302
303         my $items_rs = $biblio->items;
304         $items_rs = $items_rs->filter_by_bookable if $bookable_only;
305         my $items = $c->objects->search($items_rs);
306         return $c->render(
307             status  => 200,
308             openapi => $items
309         );
310     } catch {
311         $c->unhandled_exception($_);
312     };
313 }
314
315 =head3 add_item
316
317 Controller function that handles creating a biblio's item
318
319 =cut
320
321 sub add_item {
322     my $c = shift->openapi->valid_input or return;
323
324     try {
325         my $biblio_id = $c->param('biblio_id');
326         my $biblio    = Koha::Biblios->find( $biblio_id );
327
328         unless ($biblio) {
329             return $c->render(
330                 status  => 404,
331                 openapi => { error => "Biblio not found" }
332             );
333         }
334
335         my $body = $c->req->json;
336
337         $body->{biblio_id} = $biblio_id;
338
339         # Don't save extended subfields yet. To be done in another bug.
340         $body->{extended_subfields} = undef;
341
342         my $item = Koha::Item->new_from_api($body);
343
344         if ( !defined $item->barcode ) {
345
346             # FIXME This should be moved to Koha::Item->store
347             my $autoBarcode = C4::Context->preference('autoBarcode');
348             my $barcode     = '';
349
350             if ( !$autoBarcode || $autoBarcode eq 'OFF' ) {
351                 #We do nothing
352             }
353             elsif ( $autoBarcode eq 'incremental' ) {
354                 ($barcode) =
355                   C4::Barcodes::ValueBuilder::incremental::get_barcode;
356             }
357             elsif ( $autoBarcode eq 'annual' ) {
358                 my $year = Koha::DateUtils::dt_from_string()->year();
359                 ($barcode) =
360                   C4::Barcodes::ValueBuilder::annual::get_barcode(
361                     { year => $year } );
362             }
363             elsif ( $autoBarcode eq 'hbyymmincr' ) {
364
365                 # Generates a barcode where
366                 #  hb = home branch Code,
367                 #  yymm = year/month catalogued,
368                 #  incr = incremental number,
369                 #  reset yearly -fbcit
370                 my $now        = Koha::DateUtils::dt_from_string();
371                 my $year       = $now->year();
372                 my $month      = $now->month();
373                 my $homebranch = $item->homebranch // '';
374                 ($barcode) =
375                   C4::Barcodes::ValueBuilder::hbyymmincr::get_barcode(
376                     { year => $year, mon => $month } );
377                 $barcode = $homebranch . $barcode;
378             }
379             elsif ( $autoBarcode eq 'EAN13' ) {
380
381                 # not the best, two catalogers could add the same
382                 # barcode easily this way :/
383                 my $query = "select max(abs(barcode)) from items";
384                 my $dbh   = C4::Context->dbh;
385                 my $sth   = $dbh->prepare($query);
386                 $sth->execute();
387                 my $nextnum;
388                 while ( my ($last) = $sth->fetchrow_array ) {
389                     $nextnum = $last;
390                 }
391                 my $ean = CheckDigits('ean');
392                 if ( $ean->is_valid($nextnum) ) {
393                     my $next = $ean->basenumber($nextnum) + 1;
394                     $nextnum = $ean->complete($next);
395                     $nextnum =
396                       '0' x ( 13 - length($nextnum) ) . $nextnum;    # pad zeros
397                 }
398                 else {
399                     warn "ERROR: invalid EAN-13 $nextnum, using increment";
400                     $nextnum++;
401                 }
402                 $barcode = $nextnum;
403             }
404             else {
405                 warn "ERROR: unknown autoBarcode: $autoBarcode";
406             }
407             $item->barcode($barcode) if $barcode;
408         }
409
410         $item->store->discard_changes;
411
412         $c->render(
413             status  => 201,
414             openapi => $item->to_api
415         );
416     }
417     catch {
418         if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
419             return $c->render(
420                 status  => 409,
421                 openapi => { error => 'Duplicate barcode.' }
422             );
423         }
424         $c->unhandled_exception($_);
425     }
426 }
427
428 =head3 update_item
429
430 Controller function that handles updating a biblio's item
431
432 =cut
433
434 sub update_item {
435     my $c = shift->openapi->valid_input or return;
436
437     try {
438         my $biblio_id = $c->param('biblio_id');
439         my $item_id   = $c->param('item_id');
440         my $biblio    = Koha::Biblios->find( { biblionumber => $biblio_id } );
441         unless ($biblio) {
442             return $c->render(
443                 status  => 404,
444                 openapi => { error => "Biblio not found" }
445             );
446         }
447
448         my $item = $biblio->items->find({ itemnumber => $item_id });
449
450         unless ($item) {
451             return $c->render(
452                 status  => 404,
453                 openapi => { error => "Item not found" }
454             );
455         }
456
457         my $body = $c->req->json;
458
459         $body->{biblio_id} = $biblio_id;
460
461         # Don't save extended subfields yet. To be done in another bug.
462         $body->{extended_subfields} = undef;
463
464         $item->set_from_api($body);
465
466         $item->store->discard_changes;
467
468         $c->render(
469             status => 200,
470             openapi => $item->to_api
471         );
472     }
473     catch {
474         if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
475             return $c->render(
476                 status  => 409,
477                 openapi => { error => 'Duplicate barcode.' }
478             );
479         }
480         $c->unhandled_exception($_);
481     }
482 }
483
484 =head3 get_checkouts
485
486 List Koha::Checkout objects
487
488 =cut
489
490 sub get_checkouts {
491     my $c = shift->openapi->valid_input or return;
492
493     my $checked_in = $c->param('checked_in');
494     $c->req->params->remove('checked_in');
495
496     try {
497         my $biblio = Koha::Biblios->find( $c->param('biblio_id') );
498
499         unless ($biblio) {
500             return $c->render(
501                 status  => 404,
502                 openapi => { error => 'Object not found' }
503             );
504         }
505
506         my $checkouts =
507           ($checked_in)
508           ? $c->objects->search( $biblio->old_checkouts )
509           : $c->objects->search( $biblio->current_checkouts );
510
511         return $c->render(
512             status  => 200,
513             openapi => $checkouts
514         );
515     }
516     catch {
517         $c->unhandled_exception($_);
518     };
519 }
520
521 =head3 pickup_locations
522
523 Method that returns the possible pickup_locations for a given biblio
524 used for building the dropdown selector
525
526 =cut
527
528 sub pickup_locations {
529     my $c = shift->openapi->valid_input or return;
530
531     my $biblio = Koha::Biblios->find( $c->param('biblio_id') );
532
533     unless ($biblio) {
534         return $c->render(
535             status  => 404,
536             openapi => { error => "Biblio not found" }
537         );
538     }
539
540     my $patron = Koha::Patrons->find( $c->param('patron_id') );
541     $c->req->params->remove('patron_id');
542
543     unless ($patron) {
544         return $c->render(
545             status  => 400,
546             openapi => { error => "Patron not found" }
547         );
548     }
549
550     return try {
551
552         my $pl_set = $biblio->pickup_locations( { patron => $patron } );
553
554         my @response = ();
555         if ( C4::Context->preference('AllowHoldPolicyOverride') ) {
556
557             my $libraries_rs = Koha::Libraries->search( { pickup_location => 1 } );
558             my $libraries    = $c->objects->search($libraries_rs);
559
560             @response = map {
561                 my $library = $_;
562                 $library->{needs_override} = (
563                     any { $_->branchcode eq $library->{library_id} }
564                     @{$pl_set->as_list}
565                   )
566                   ? Mojo::JSON->false
567                   : Mojo::JSON->true;
568                 $library;
569             } @{$libraries};
570         }
571         else {
572
573             my $pickup_locations = $c->objects->search($pl_set);
574             @response = map { $_->{needs_override} = Mojo::JSON->false; $_; } @{$pickup_locations};
575         }
576
577         return $c->render(
578             status  => 200,
579             openapi => \@response
580         );
581     }
582     catch {
583         $c->unhandled_exception($_);
584     };
585 }
586
587 =head3 get_items_public
588
589 Controller function that handles retrieving biblio's items, for unprivileged
590 access.
591
592 =cut
593
594 sub get_items_public {
595     my $c = shift->openapi->valid_input or return;
596
597     my $biblio = Koha::Biblios->find(
598         $c->param('biblio_id'),
599         { prefetch => ['items'] }
600     );
601
602     unless ( $biblio ) {
603         return $c->render(
604             status  => 404,
605             openapi => {
606                 error => "Object not found."
607             }
608         );
609     }
610
611     return try {
612
613         my $patron = $c->stash('koha.user');
614
615         my $items_rs = $biblio->items->filter_by_visible_in_opac({ patron => $patron });
616         my $items    = $c->objects->search( $items_rs );
617         return $c->render(
618             status  => 200,
619             openapi => $items
620         );
621     }
622     catch {
623         $c->unhandled_exception($_);
624     };
625 }
626
627 =head3 set_rating
628
629 Set rating for the logged in user
630
631 =cut
632
633
634 sub set_rating {
635     my $c = shift->openapi->valid_input or return;
636
637     my $biblio = Koha::Biblios->find( $c->param('biblio_id') );
638
639     unless ($biblio) {
640         return $c->render(
641             status  => 404,
642             openapi => {
643                 error => "Object not found."
644             }
645         );
646     }
647
648     my $patron = $c->stash('koha.user');
649     unless ($patron) {
650         return $c->render(
651             status => 403,
652             openapi =>
653                 { error => "Cannot rate. Reason: must be logged-in" }
654         );
655     }
656
657     my $body         = $c->req->json;
658     my $rating_value = $body->{rating};
659
660     return try {
661
662         my $rating = Koha::Ratings->find(
663             {
664                 biblionumber   => $biblio->biblionumber,
665                 borrowernumber => $patron->borrowernumber,
666             }
667         );
668         $rating->delete if $rating;
669
670         if ( $rating_value ) { # Cannot set to 0 from the UI
671             $rating = Koha::Rating->new(
672                 {
673                     biblionumber   => $biblio->biblionumber,
674                     borrowernumber => $patron->borrowernumber,
675                     rating_value   => $rating_value,
676                 }
677             )->store;
678         };
679         my $ratings =
680           Koha::Ratings->search( { biblionumber => $biblio->biblionumber } );
681         my $average = $ratings->get_avg_rating;
682
683         return $c->render(
684             status  => 200,
685             openapi => {
686                 rating  => $rating && $rating->in_storage ? $rating->rating_value : undef,
687                 average => $average,
688                 count   => $ratings->count
689             },
690         );
691     }
692     catch {
693         $c->unhandled_exception($_);
694     };
695 }
696
697 =head3 add
698
699 Controller function that handles creating a biblio object
700
701 =cut
702
703 sub add {
704     my $c = shift->openapi->valid_input or return;
705
706     try {
707         my $headers = $c->req->headers;
708
709         my $flavour = $headers->header('x-record-schema');
710         $flavour //= C4::Context->preference('marcflavour');
711
712         my $record;
713
714         my $frameworkcode = $headers->header('x-framework-id');
715         my $content_type  = $headers->content_type;
716
717         if ( $content_type =~ m/application\/marcxml\+xml/ ) {
718             $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
719         }
720         elsif ( $content_type =~ m/application\/marc-in-json/ ) {
721             $record = MARC::Record->new_from_mij_structure( $c->req->json );
722         }
723         elsif ( $content_type =~ m/application\/marc/ ) {
724             $record = MARC::Record->new_from_usmarc( $c->req->body );
725         }
726         else {
727             return $c->render(
728                 status  => 406,
729                 openapi => [
730                     "application/marcxml+xml",
731                     "application/marc-in-json",
732                     "application/marc"
733                 ]
734             );
735         }
736
737         my ( $duplicatebiblionumber, $duplicatetitle );
738             ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($record);
739
740         my $confirm_not_duplicate = $headers->header('x-confirm-not-duplicate');
741
742         return $c->render(
743             status  => 400,
744             openapi => {
745                 error => "Duplicate biblio $duplicatebiblionumber",
746             }
747         ) unless !$duplicatebiblionumber || $confirm_not_duplicate;
748
749         my ( $biblionumber, $oldbibitemnum );
750             ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
751
752         $c->render(
753             status  => 200,
754             openapi => { id => $biblionumber }
755         );
756     }
757     catch {
758         $c->unhandled_exception($_);
759     };
760 }
761
762 =head3 update
763
764 Controller function that handles modifying an biblio object
765
766 =cut
767
768 sub update {
769     my $c = shift->openapi->valid_input or return;
770
771     my $biblio = Koha::Biblios->find( $c->param('biblio_id') );
772
773     if ( ! defined $biblio ) {
774         return $c->render(
775             status  => 404,
776             openapi => { error => "Object not found" }
777         );
778     }
779
780     try {
781         my $headers = $c->req->headers;
782
783         my $flavour = $headers->header('x-record-schema');
784         $flavour //= C4::Context->preference('marcflavour');
785
786         my $frameworkcode = $headers->header('x-framework-id') || $biblio->frameworkcode;
787
788         my $content_type = $headers->content_type;
789
790         my $record;
791
792         if ( $content_type =~ m/application\/marcxml\+xml/ ) {
793             $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
794         }
795         elsif ( $content_type =~ m/application\/marc-in-json/ ) {
796             $record = MARC::Record->new_from_mij_structure( $c->req->json );
797         }
798         elsif ( $content_type =~ m/application\/marc/ ) {
799             $record = MARC::Record->new_from_usmarc( $c->req->body );
800         }
801         else {
802             return $c->render(
803                 status  => 406,
804                 openapi => [
805                     "application/json",
806                     "application/marcxml+xml",
807                     "application/marc-in-json",
808                     "application/marc"
809                 ]
810             );
811         }
812
813         ModBiblio( $record, $biblio->id, $frameworkcode );
814
815         $c->render(
816             status  => 200,
817             openapi => { id => $biblio->id }
818         );
819     }
820     catch {
821         $c->unhandled_exception($_);
822     };
823 }
824
825 =head3 list
826
827 Controller function that handles retrieving a single biblio object
828
829 =cut
830
831 sub list {
832     my $c = shift->openapi->valid_input or return;
833
834     my @prefetch = qw(biblioitem);
835     push @prefetch, 'metadata'    # don't prefetch metadata if not needed
836       unless $c->req->headers->accept =~ m/application\/json/;
837
838     my $rs = Koha::Biblios->search( undef, { prefetch => \@prefetch });
839     my $biblios = $c->objects->search_rs( $rs, [(sub{ $rs->api_query_fixer( $_[0], '', $_[1] ) })] );
840
841     return try {
842
843         if ( $c->req->headers->accept =~ m/application\/json(;.*)?$/ ) {
844             return $c->render(
845                 status => 200,
846                 json   => $c->objects->to_api( $biblios ),
847             );
848         }
849         elsif (
850             $c->req->headers->accept =~ m/application\/marcxml\+xml(;.*)?$/ )
851         {
852             $c->res->headers->add( 'Content-Type', 'application/marcxml+xml' );
853             return $c->render(
854                 status => 200,
855                 text   => $biblios->print_collection('marcxml')
856             );
857         }
858         elsif (
859             $c->req->headers->accept =~ m/application\/marc-in-json(;.*)?$/ )
860         {
861             $c->res->headers->add( 'Content-Type', 'application/marc-in-json' );
862             return $c->render(
863                 status => 200,
864                 data   => $biblios->print_collection('mij')
865             );
866         }
867         elsif ( $c->req->headers->accept =~ m/application\/marc(;.*)?$/ ) {
868             $c->res->headers->add( 'Content-Type', 'application/marc' );
869             return $c->render(
870                 status => 200,
871                 text   => $biblios->print_collection('marc')
872             );
873         }
874         elsif ( $c->req->headers->accept =~ m/text\/plain(;.*)?$/ ) {
875             return $c->render(
876                 status => 200,
877                 text   => $biblios->print_collection('txt')
878             );
879         }
880         else {
881             return $c->render(
882                 status  => 406,
883                 openapi => [
884                     "application/json",         "application/marcxml+xml",
885                     "application/marc-in-json", "application/marc",
886                     "text/plain"
887                 ]
888             );
889         }
890     }
891     catch {
892         $c->unhandled_exception($_);
893     };
894 }
895
896 =head3 merge
897
898 Controller function that handles merging two biblios. If an optional
899 MARCXML is provided as the request body, this MARCXML replaces the
900 bibliodata of the merge target biblio. Syntax format inside the request body
901 must match with the Marc format used into Koha installation (MARC21 or UNIMARC)
902
903 =cut
904
905 sub merge {
906     my $c                = shift->openapi->valid_input or return;
907     my $ref_biblionumber = $c->param('biblio_id');
908     my $json             = decode_json( $c->req->body );
909     my $bn_merge         = $json->{'biblio_id_to_merge'};
910     my $framework        = $json->{'framework_to_use'};
911     my $rules            = $json->{'rules'};
912     my $override_rec     = $json->{'datarecord'};
913
914     if ( ( !defined $rules ) || ( $rules eq '' ) ) { $rules        = 'override'; }
915     if ( ( !defined $override_rec ) )              { $override_rec = ''; }
916     if ( ( !defined $framework ) )                 { $framework    = ''; }
917
918     my $biblio = Koha::Biblios->find($ref_biblionumber);
919     if ( not defined $biblio ) {
920         return $c->render(
921             status => 404,
922             json   => { error => sprintf( "[%s] biblio to merge into not found", $ref_biblionumber ) }
923         );
924     }
925     my $frombib = Koha::Biblios->find($bn_merge);
926     if ( not defined $frombib ) {
927         return $c->render(
928             status => 404,
929             json   => { error => sprintf( "[%s] from which to merge not found", $bn_merge ) }
930         );
931     }
932
933     if ( ( $rules eq 'override_ext' ) && ( $override_rec eq '' ) ) {
934         return $c->render(
935             status => 404,
936             json   => {
937                 error =>
938                     "With the rule 'override_ext' you need to insert a bib record in marc-in-json format into 'record' field."
939             }
940         );
941     }
942
943     if ( ( $rules eq 'override' ) && ( $framework ne '' ) ) {
944         return $c->render(
945             status => 404,
946             json   => { error => "With the rule 'override' you can not use the field 'framework_to_use'." }
947         );
948     }
949
950     my $results;
951     eval {
952         if ( $rules eq 'override_ext' ) {
953             my $record = MARC::Record::MiJ->new_from_mij_structure($override_rec);
954             $record->encoding('UTF-8');
955             if ( $framework eq '' ) { $framework = $biblio->frameworkcode; }
956             my $chk = ModBiblio( $record, $ref_biblionumber, $framework );
957             if ( $chk != 1 ) { die "Error on ModBiblio"; }    # ModBiblio returns 1 if everything as gone well
958             my @biblio_ids_to_merge = ($bn_merge);
959             $results = $biblio->merge_with( \@biblio_ids_to_merge );
960         }
961         if ( $rules eq 'override' ) {
962             my @biblio_ids_to_merge = ($bn_merge);
963             $results = $biblio->merge_with( \@biblio_ids_to_merge );
964         }
965     };
966     if ($@) {
967         return $c->render( status => 400, json => { error => $@ } );
968     } else {
969         $c->respond_to(
970             mij => {
971                 status => 200,
972                 format => 'mij',
973                 data   => $biblio->metadata->record->to_mij
974             }
975         );
976     }
977 }
978
979 1;