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